电路仿真系统三次迭代作业总结(作业4-6)
电路仿真系统三次迭代作业总结(作业4-6)
一、前言
1.1 作业背景
本次系列作业围绕"数字逻辑电路仿真系统"展开,共进行三次迭代开发。电路仿真需要模拟信号在逻辑门之间的传播,计算每个节点的逻辑值。三次作业从基础逻辑门开始,逐步增加了多输出器件、子电路定义与实例化等功能。
1.2 知识点分布
作业次序 核心知识点 新增技术点
第四次作业 信号传播、拓扑排序 逻辑门(与、或、非、异或、同或)、信号表
第五次作业 复杂器件建模 选择器、译码器、多输出器件、多级传播
第六次作业 层次化设计 子电路定义与实例化、递归展开、连接错误检查
1.3 难度变化分析
第四次作业是最基础的电路仿真入门,主要目标是实现信号在网络中的传播和逻辑门计算。难点在于循环传播的处理(需要迭代直至稳定)和输出顺序的排序。
第五次作业难度明显提升,引入了多输入多输出器件(M、Z、F),需要处理更复杂的引脚映射关系。代码量比第四次翻倍,新增了选择器、译码器等器件的逻辑。
第六次作业是功能最复杂的一次,新增了子电路(C1、C2...)的定义、展开和实例化。需要实现子电路的递归展开和连接错误检查,涉及多层嵌套的处理。
二、设计与分析
2.1 第四次作业
2.1.1 类图设计

图1 第四次作业类图
类职责分析:
类名 职责描述 SRP符合度
Main 主控类:输入解析、信号传播、逻辑计算、输出排序 部分符合
GateInfo 器件信息存储:名称、类型、编号、引脚映射 符合
设计亮点:GateInfo类独立存储器件信息,与主逻辑分离。
存在问题:Main类承担了过多职责(解析、传播、计算、排序),建议拆分为InputParser、SignalPropagator、GateEvaluator等独立类。
2.1.2 复杂度分析

图2 第四次作业代码复杂度报表
关键指标分析:
方法 圈复杂度(v(G)) 分析
eval() 约8-10 包含多层循环和条件判断,复杂度较高
procLink() 约6 链接解析逻辑复杂
operate() 约6 多个逻辑门分支判断
flow() 约5 信号传播逻辑
main() 约4 主流程控制
心得:第四次作业的最大圈复杂度约10,主要来自eval()方法中循环遍历器件、检查输入就绪状态、计算输出的逻辑。虽然代码能正常工作,但eval()方法同时承担了输入检查和逻辑计算两个职责,建议拆分。
2.2 第五次作业
2.2.1 类图设计

图3 第五次作业类图
类职责分析:
类名 职责描述 SRP符合度
Main 主控类:输入解析、信号传播、器件评估、输出格式化 部分符合
Gate 器件模型:引脚管理、输入输出引脚计算 符合
设计亮点:Gate类独立负责引脚管理,支持多种器件类型(A/O/N/X/Y/S/M/Z/F)。
存在问题:Main.evaluate()方法包含多个switch分支(A/O/N/X/Y/S/M/Z/F),每个分支逻辑不同,建议使用策略模式或工厂模式重构。
2.2.2 复杂度分析

图4 第五次作业代码复杂度报表
关键指标分析:
方法 圈复杂度(v(G)) 分析
evaluate() 约12-15 包含9种器件的评估逻辑,复杂度最高
evalGate() 约10 多分支switch,处理不同器件类型
parseLink() 约6 链接解析逻辑复杂
propagate() 约4 信号传播逻辑
心得:第五次作业的最大圈复杂度显著上升,达到约12-15。evaluate()方法中的switch分支处理了A/O/N/X/Y/S/M/Z/F九种器件,是复杂度最高的来源。这是典型的"长方法"坏味道,后续应通过引入多态或策略模式来降低复杂度。
2.3 第六次作业
2.3.1 类图设计

图5 第六次作业类图
类职责分析:
类名 职责描述 SRP符合度
Main 主控类:子电路解析、展开、实例化、仿真 部分符合
SubCircuit 子电路定义:输入输出引脚、内部连接 符合
SubRef 子电路引用:电路编号和引脚名称 符合
Gate 器件模型:引脚管理 符合
设计亮点:
- 子电路定义(SubCircuit)与引用(SubRef)分离,层次清晰
- 支持子电路递归展开,通过多次迭代解决相互引用问题
- 连接错误检查覆盖了多种异常情况
存在问题:Main类过于庞大(约300行),包含了子电路解析、展开、实例化、错误检查、仿真等全部逻辑,严重违反SRP原则。
2.3.2 复杂度分析

图6 第六次作业代码复杂度报表
关键指标分析:
方法 圈复杂度(v(G)) 分析
main() 约15-18 包含10个阶段的主流程,复杂度最高
checkError() 约10 包含多种错误检查逻辑
expandSubCircuit() 约8 子电路展开逻辑复杂
processConnection() 约6 连接处理逻辑
心得:第六次作业的最大圈复杂度达到15-18,是三次作业中最高的。main()方法包含了子电路解析、输入解析、错误检查、连接处理、仿真等多个阶段,代码过长(约80行),严重影响了可读性和可维护性。虽然功能正确实现,但架构设计存在较大改进空间。
2.4 三次作业复杂度对比
指标 第四次 第五次 第六次 趋势分析
类数量 2 2 4 逐步增加
最大圈复杂度 约8-10 约12-15 约15-18 随业务复杂度上升
总语句数 约180 约300 约400 代码量持续翻倍
main方法行数 约25行 约30行 约80行 main方法膨胀严重
三、采坑心得
3.1 第四次作业的坑:信号传播顺序导致结果错误
问题描述:我最初实现的传播逻辑只做一次遍历,导致多层器件的输出无法正确传播。
错误输入:
text
INPUT: A-1 B-0
[A B] A1(2)-0
错误输出:输出为-1(未知),未计算
原因分析:只做了一次传播,第一层器件的输出作为第二层器件的输入时,第二层器件还未被评估。
解决方案:使用do-while循环,重复传播和计算,直到没有新信号变化为止。
boolean changed;
do {
changed = false;
changed |= propagate();
changed |= evaluate();
} while (changed);
教训:电路仿真中信号传播是迭代过程,不能只做一次。
3.2 第五次作业的坑:器件引脚编号理解错误
问题描述:对于M(多路选择器)器件,我错误地认为输入引脚从1开始编号,实际从0开始。
错误输入:M器件的输入引脚编号混乱
错误输出:输出为-1(未知)或错误值
原因分析:没有仔细阅读器件规格说明,混淆了引脚编号规则。
解决方案:梳理清楚每种器件的引脚分配:
• 普通门(A/O/N/X/Y):输入从1开始,输出为0
• S(选择器):控制位0,输入1
• M(多路选择器):控制位0/1/2,数据输入从3开始
• Z(译码器):控制位从0开始,数据输入从ctrlCnt开始
• F(分配器):控制位从0开始,数据输入从ctrlCnt开始
教训:器件规格必须仔细阅读,引脚编号规则决定了整个仿真结果的正确性。
3.3 第六次作业的坑:子电路展开无限递归
问题描述:当子电路A引用子电路B,子电路B又引用子电路A时,展开逻辑陷入了无限循环。
错误输入:C1引用C2,C2引用C1
错误输出:程序卡死或栈溢出
原因分析:没有处理循环引用的情况。
解决方案:维护一个已展开集合(instantiatedSCs),每次展开前检查是否已经展开过。
if (instantiatedSCs.add(num)) {
// 执行展开
}
教训:处理递归结构时,必须考虑循环引用的可能性,使用已访问集合防止无限递归。
3.4 第六次作业的坑:连接错误检查遗漏
问题描述:初版没有做连接错误检查,导致非法输入时程序继续运行,输出错误结果。
错误输入:连接中多个信号源连接到了同一个输出
错误输出:未报错,直接使用最后一个值
原因分析:没有实现错误检查逻辑。
解决方案:实现checkError方法,检查:
- 是否包含多个输入(连接中不能有多个信号源)
- 是否包含零个输入(必须有一个信号源)
- 是否包含零个输出(必须有一个目标)
- 输入和输出是否顺序错误(信号源不能在目标之后)
教训:输入校验是程序鲁棒性的重要组成部分,应在处理逻辑之前完成。
四、改进建议
4.1 第四次作业的改进
问题 改进建议
Main类职责过重 拆分为InputParser、Propagator、Evaluator、OutputSorter
eval()方法过长 将输入检查逻辑和计算逻辑分离
硬编码器件类型 使用枚举类型管理器件类型
4.2 第五次作业的改进
问题 改进建议
evaluate()的switch分支过多 使用策略模式,每种器件独立成类
全局静态变量过多 封装为AppContext对象,通过依赖注入传递
引脚管理分散 统一由Gate类的inPins/outPins方法管理
4.3 第六次作业的改进
问题 改进建议
main()方法过长 拆分为独立的阶段方法:parseSubCircuits()、expandAll()、checkErrors()、simulate()
SubCircuit展开逻辑复杂 分离解析和展开两个阶段
错误检查逻辑分散 统一到ErrorChecker类中
五、总结
5.1 学到了什么
技术层面:
- 信号传播算法:通过多次迭代实现信号在网络中的稳定传播
- 器件建模:不同器件(与门、或门、选择器、译码器)的统一建模方法
- 递归展开:子电路的层次化定义与实例化
- 错误检查:连接语义的正确性验证(单源单目的)
- 拓扑排序思想:通过迭代逐步计算所有节点的值
工程层面:
- 代码量从180行增长到400行,必须通过良好的架构控制复杂度
- 每个阶段(解析到传播到计算到输出)应该独立,便于调试和测试
- 输入校验应该在处理逻辑之前完成,避免污染数据
5.2 需要继续学习的方向
- 设计模式:策略模式(器件评估)、工厂模式(器件创建)
- 软件架构:将电路仿真拆分为Parser、Simulator、Reporter三个独立模块
- 错误处理:更优雅的异常处理机制,而非System.exit()
- 单元测试:为每种器件编写独立的测试用例
5.3 对课程的建议
- 理论课:建议在讲设计模式时,用本次作业的器件评估(多种器件类型)作为策略模式的案例
- 实验课:可以安排一次代码重构专题,针对Main类过大的问题
- 作业迭代:建议在第五次和第六次之间增加一次小作业,专门练习类拆分

浙公网安备 33010602011771号