数字电路模拟程序——三次作业总结
一、前言
1.1 作业知识点概览
三次作业围绕“数字电路逻辑模拟”这一真实硬件抽象场景,从基础逻辑门到组合电路元件,再到子电路与异常处理,层层递进地考查了面向对象设计的核心概念与软件工程实践。
- 作业1(V1)知识点:封装、继承与多态、抽象类、Map集合、手写冒泡排序、信号级联传播迭代算法、基本输入解析、空值安全处理。
- 作业2(V2)知识点:继承体系扩展、多输出引脚管理、控制引脚识别、邻接表与队列拓扑传播、复杂元件建模(三态门、译码器、数据选择器、数据分配器)。
- 作业3(V3)知识点:组合模式(Composite Pattern)、子电路定义与实例化、递归展开算法、嵌套子电路处理、端口映射、循环引用检测、异常输入检测与优先级输出、正则表达式高级解析。
1.2 题量与难度分析
- 题量:三次作业均为单题,代码规模逐次增长。V1约375行/8个类,V2约597行/7个类,V3约670行/15个类。
- 难度:渐进式陡升。
- V1为基础入门,理解继承和多态,手写排序,较为基础简单。
- V2强调多引脚元件和复杂输出,需设计灵活的邻接传播,难度中等偏上。
- V3引入子电路递归展开和优先级异常检测,设计约束严格,难度较高。
二、设计与分析
2.1 第一次作业(V1)源码分析
2.1.1 度量报告

使用SourceMonitor对V1代码进行静态分析,核心指标如下:
| 参数 | 数值 | 解读 |
|---|---|---|
| 总代码行数 | 375 | 含注释和空行 |
| 有效语句数 | 244 | 不含空行和注释 |
| 分支语句占比 | 22.1% | 因门类型判断和引脚检查较多 |
| 方法调用语句数 | 134 | |
| 注释率 | 5.3% | |
| 类的数量 | 8 | Main、Door抽象类、5个具体门、Sign |
| 每个类平均方法数 | 4.00 | 方法颗粒度适中 |
| 每个方法平均语句数 | 5.72 | 方法较精简 |
| 最复杂方法 | Main.main() | 圈复杂度最高 |
报告核心解读:代码整体结构清晰,类职责划分合理——每个门类只负责自身逻辑,符合单一职责原则。但主方法承担了输入解析、门创建、连接处理、级联循环和输出排序等多个职责,导致其圈复杂度偏高。注释率5.3%为三次最高,体现了初始代码较好的文档习惯。
2.2 第二次作业(V2)源码分析
2.2.1 度量报告

| 参数 | 数值 | 较V1变化 |
|---|---|---|
| 总代码行数 | 597 | +59.2% |
| 有效语句数 | 501 | +105.3% |
| 分支语句占比 | 24.0% | +1.9% |
| 方法调用语句数 | 201 | +50.0% |
| 注释率 | 3.7% | -1.6% |
| 类的数量 | 7 | -1 |
| 每个类平均方法数 | 8.43 | +110.8% |
| 每个方法平均语句数 | 7.92 | +38.5% |
| 最复杂方法 | Main.main() | 圈复杂度持续升高 |
报告核心解读:V2代码行数增长59%,有效语句数翻倍,体现了新增四种组合元件(三态门、译码器、选择器、分配器)带来的逻辑复杂度提升。每个类平均方法数从4.00骤增至8.43,显示类职责有所膨胀。注释率从5.3%下降到3.7%,说明随着代码量增长,文档维护被忽视。
2.3 第三次作业(V3)源码分析
2.3.1 度量报告

| 参数 | 数值 | 较V2变化 |
|---|---|---|
| 总代码行数 | 670 | +12.2% |
| 有效语句数 | 608 | +21.4% |
| 分支语句占比 | 27.3% | +3.3% |
| 方法调用语句数 | 312 | +55.2% |
| 注释率 | 2.5% | -1.2% |
| 类的数量 | 15 | +8 |
| 每个类平均方法数 | 3.60 | -57.3% |
| 每个方法平均语句数 | 9.30 | +17.4% |
| 最复杂方法 | Main.main() | 圈复杂度进一步升高 |
报告核心解读:V3代码行数增长趋缓(+12.2%),但类数量从7个激增至15个,新增了 PinMeta、PinKind、GateInfo、SubCkt 等辅助类,以及异常处理的枚举和验证逻辑。引入了子电路展开机制,代码从“单层门级模拟”升级为“层次化电路模拟”。每个类平均方法数从8.43降至3.60。分支语句占比达27.3%为三次最高,反映了异常检测和引脚类型判断的复杂性。注释率降至2.5%,代码可读性亟待提升。
三、踩坑心得
3.1 作业1(V1)踩坑
- 引脚编号与门名解析:与门/或门格式为 A(2)1,非门等为 X5,需分别使用正则匹配,初始时混淆了括号位置导致解析失败。
- 级联传播终止条件:最初用 while(changed) 全量遍历,效率低且可能死循环。改用队列后,每个门只计算一次,效率显著提升。
- 排序时类型顺序:自然字母序为 A、N、O、X、Y,但题目要求 A、O、N、X、Y,需对 O 特殊处理(ASCII -2)。
3.2 作业2(V2)踩坑
- 多输出元件的传播:译码器有多个输出引脚,需在 calcAndGetOutPins 中返回所有输出引脚列表,并在 printOutput 中按格式输出。最初只处理单引脚导致漏算。
- 数据选择器引脚顺序:选择位从0开始编号,数据引脚从 selectBits 开始,输出引脚为 selectBits + numData。偏移量计算错误会导致读取错误的数据输入。
- 三态门无效状态:当控制为0时,输出引脚无有效值,printOutput 应跳过。但 computed 仍置为 true,需在输出时检查 outputs 是否包含该引脚,否则会输出错误值。
3.3 作业3(V3)踩坑
- 子电路端口映射:子电路实例化时,需将外部输入信号映射到子电路的输入端口,并将子电路输出端口映射到外部目标。映射关系使用 instanceInputMap 维护,但嵌套子电路时需要传递前缀,稍有不慎就会映射错误。
- 循环引用检测:使用 expanding 集合在展开前标记,若再次遇到同一实例则报错。但需确保展开完成后正确移除,否则后续展开可能误判。
- 异常优先级处理:题目规定按顺序检查优先级,validateWire 中必须严格按照顺序实现。
- 带前缀的门名解析:展开后门名加上实例前缀(如 C2-X1),正则表达式需支持前缀匹配。初始时未考虑前缀,导致解析失败。
四、改进建议
4.1 代码结构优化
- 真正实现组合模式:当前采用展开策略,虽可行但增加了代码复杂度。若将 SubCkt 也作为 Door 的子类,实现 compute 时递归调用内部门的计算,则无需展开,更符合面向对象设计,且便于处理反馈电路(后续迭代)。
- 工厂模式重构:makeDoor 和 parseGate 中的长 if-else 可抽取为 DoorFactory,通过类型字符串创建实例,便于扩展新元件,符合开闭原则。
- 解析器分离:将输入解析、子电路解析、异常检测拆分为独立类(如 Parser、Validator),降低 Main 的圈复杂度,提高可测试性。
4.2 注释与文档
- 三次作业注释率从5.3%持续下降至2.5%,为所有指标中趋势最差的。建议为每个类、方法添加功能说明,特别是对展开算法和异常检测逻辑的注释。团队开发中注释率通常要求不低于20%,当前水平亟待改善。
4.3 错误处理增强
- 当前异常检测仅报错并退出程序,可考虑支持多行错误累积输出,或提供更友好的错误定位信息(如行号)。
- 对 Integer.parseInt 等数值解析增加 try-catch 并提供明确的错误上下文。
4.4 性能优化
- 子电路展开后,门数量可能急剧增加,可考虑缓存展开结果,避免重复展开同一子电路实例。
- 使用位运算优化译码器/选择器的选择逻辑,提高计算效率。
4.5 持续改进路径
- 第一阶段:真正引入组合模式,将子电路视为 Door 子类,统一处理基本门和复合电路。
- 第二阶段:增加单元测试(JUnit),为每个门类、展开算法和异常检测编写测试用例,确保重构后功能正确。
- 第三阶段:引入配置文件,支持用户自定义新元件类型,无需修改源代码。
五、总结
5.1 学习成果
通过这三次作业,我从基础逻辑门逐步进阶到复杂电路系统的模拟,深刻理解了以下方面:
设计模式实践:从V1到V3,我经历了从“面向过程思维”到“面向对象设计”的转变。V3引入的组合模式(虽未完全实现)让我认识到,将子电路视为元件能极大简化层次结构的处理。递归展开算法锻炼了我处理嵌套结构的能力,学会了防止循环引用的技巧。
面向对象设计原则:
- 开闭原则:新增元件只需添加子类,无需修改原有代码,V2中新增四种元件而未改动V1的五个门类即是明证。
- 单一职责原则:每个门类只负责自身逻辑,但主类仍承担过多职责(V3中 main 方法圈复杂度为全类最高),需进一步拆分。
- 依赖倒置:Main依赖抽象 Door 类而非具体门类,降低了耦合度。
算法与数据结构:
- 邻接表+队列的拓扑传播是对有向无环图算法的实际应用。
- 子电路展开则是树形结构的扁平化处理,加深了对递归和迭代的理解。
- 手写排序算法(V1冒泡排序)强化了基础算法能力。
鲁棒性设计:V3中异常检测的优先级处理让我学会在复杂输入中按规则识别错误,并给出清晰反馈。五种异常类型的分类和优先级排序,锻炼了需求分析和系统设计能力。
度量分析能力:通过SourceMonitor的量化数据,我学会用指标评估代码质量——注释率、分支语句占比、类与方法数量、圈复杂度等。例如注释率从5.3%持续下降至2.5%的警示,让我意识到文档维护的重要性。
5.2 待深入学习方向
- 组合模式与访问者模式:可用于统一处理基本门和子电路,并支持不同操作(计算、输出、异常检查),避免冗长的类型判断。
- 单元测试:为每个门类和展开算法编写测试用例,确保重构后功能正确,提高代码质量。
- 并发模拟:真实电路信号传播是并行的,可研究用多线程或事件驱动模拟更真实的时序行为。
- 语法解析技术:V3的输入格式已较为复杂,后续若增加更多语法元素,可引入语法解析器生成器(如ANTLR)。
5.3 对课程的建议
- 作业的渐进式迭代设计非常好,从单门到组合门到子电路,逻辑清晰、层层递进,非常接近真实项目的开发模式。
- 建议后续增加代码评审环节,由教师展示优秀作业和典型问题,促进同学之间的交流学习。
- 可提前提供SourceMonitor等工具的使用教程,减少环境配置耗时,让学生将更多精力放在代码设计和算法实现上。
- 实验课可增加一次重构实验,让学生将自己的V1代码升级到V3,体会迭代开发的优势和重构的重要性。
5.4 结束语
三次作业从简单的门级模拟,到组合元件,再到子电路和异常检测,让我完整经历了一个中型软件系统的设计与迭代过程。每次迭代都迫使我在扩展性、可维护性和正确性之间寻找平衡。
从SourceMonitor的量化分析中,我清晰地看到了代码演进的轨迹:行数增长、类数波动、注释率下降、分支复杂度上升——这些数据不仅反映了功能的增加,也暴露了设计上的取舍。
这让我深刻理解了:好的设计不是一蹴而就的,而是在不断迭代和重构中逐步演进的。感谢老师设计了这样循序渐进的课程体系,让我在实践中真正掌握了面向对象设计的核心思想,为后续的软件开发学习奠定了坚实基础。

浙公网安备 33010602011771号