数字电路模拟程序迭代博客
(1)前言:
总体而言此次迭代相较上次航空题目集难度上升许多,在此次作业集中,我们的程序循序渐进完成数字电路系统从基础逻辑门元件构成电路、多功能组合器件拓展到多输入输出组合的数据选择器和增加子电路以及输入异常检测,三次作业难度逐步递增,代码体量、业务逻辑要求逐层升级,同时在对于题目的理解上还要求有一定的数理知识存储。
数字电路模拟程序使用到了包括继承与抽象,重写,多态之类的基础面向对象知识,也包含HashMap/ArrayList存储电路拓扑等集合框架知识,还有一些正则表达式和算法上的涉及譬如递归算法。知识涉猎广泛而繁多,每次作业代码体量大,但上下作业之间关联紧密,对于迭代代码的修改大有裨益,可复用性高,便于代码修改后直接利用。
(2)设计与分析:
作业一:
类图:

生成报表:

分析:
根据类图可知第一次作业整体采用抽象父类+子类继承的架构,有:基础单元:Pins引脚类,抽象顶层Conpute元件父类以及五大继承子类来实现抽象父类,电路管理Circuit类和解析Parser类。Pins作为引脚类只提供基础的getter/setter方法,Compute抽取各个子类的共有属性和方法。五个子类重写方法实现各自的逻辑。Circuit是全局的重点,使用doors,primaryInputs,inoutToSource三层维护,同时利用递归方法完成计算。
生成报表数据:文件数:10 个源码文件,总行 :427 行,有效执行语句 :252 行注释占比%:11.7%,类数量:10个,平均每个类方法数:4.50,平均圈复杂度:2.10,最大复杂度:11,分支占比:19.8%。
由数据可得:作业一作为初代代码体量是三次作业中最小的,逻辑分支少,最大圈复杂度集中在递归求值区域。单类职责较集中,分支占比主要体现在门电路求职和引脚判断中。
这次的设计严格遵守了面向对象基础思想,类与类的职责分明,代码复用度较高,耦合性也很低。但同时也存在引脚没有类型区分,递归要求过于严格导致重复计算等问题,有效执行语句占比不高,可读性也有待提高。
作业二:
类图:
生成报表:

分析:
第二次作业拓展了三态门译码器选择器和分配器等组合元件。
从类图分析来看:这次作业在原有基础上重构了Compute类,不再依靠硬编码判断引脚功能,新增了Map映射表给每个引脚绑定标签,新增四类组合器件继承于Compte,同时升级了Circuit。
生成报表数据:文件数 :14 个,总行:608 行,有效语句:350 行。注释占比%:5.9%,类数量 :14 个(新增TriStateGate、Decoder、Multiplexer、Demultiplexer4 个器件类);平均圈复杂度:1.83,最大圈复杂度:13(集中在译码器、分配器多输出引脚遍历逻辑);分支占比% :19.1%(多输出器件增加多层循环分支)。
由数据可得:这次的作业因为引脚类型映射优化使得分支复杂度得到优化,这也是此次作业相较于上次作业较大的改动之一。
作业三
类图:

生成报表:

分析:
第三次作业作为作业集的最终题目,由于前面设计的不完备选择重构了分层架构。
由类图可得:由于最终迭代题目难度上升,最初代码已无法实现性能优化,故采取了四层分层解耦架构重新实现。Model作为底层核心,包括了抽象父类,门类和信号类。Parser依旧实现解析功能。这次重构使得耦合性大大降低,代码复用性得到提升,不过也存在子电路逻辑分支过多等问题,同时无法通过部分多电路测试点。
生成报表数据:文件数:6 个,总行:414 行,有效语句 :387 行;注释占比% :3.1%,类数量 :13 个,平均圈复杂度:4.59,最大圈复杂度 :23,分支占比% :27.9%。
由生成数据可知:这次作业的有效语句得到大幅度提升,作为最后的作业相较于前两次逻辑更完整(增添了验证数据合法性方法等等)。同时虽然复杂度提升但重构后的分层解耦使得方法职责单一,没有过长的嵌套逻辑,便于阅读。分支占比的较大提升则得益于合法性校验以及一些子电路匹配。
(3)采坑心得:
第一次作业:
1.递归重复计算,容易导致运行超时,后续作业中通过新增HashMap在计算后保存引脚,直接读取缓存,不用每次获取引脚之都从头递归。
2用硬编码判断引脚,使得后面迭代代码复用性和扩展性比较差,这一问题在第三次作业通过重构得到了解决。
第二次作业:
1.新增三态门控制引脚后,原有硬编码判断引脚1、2为输入直接失效,需要大规模修改判断代码。同样是第一次作业设计不当遗留下来的问题,后续作业如果有类似题目不可用固定编号来判断,可复用性太差了,题目一进行升级或者改动后就容易现原形,后面还要从头再写。
2.实现过程中有一个测试点一直过不去,我觉得可能是没有考虑无效元件,后面新增hasValidOutput()方法来统一判断器件是否拥有完整有效输入,无效器件直接过滤不输出。
第三次作业:
1.子电路实例冲突:同一个子电路C1在主电路中被调用2次,两次实例引脚完全重名,信号互相覆盖导致仿真结果完全错误。后续新增了全局实例计数器,隔离不同的实例。
2.输入引脚序号移位:题目引脚编号从1开始,但是数组下标从0开始,导致在连线绑定引脚时所有门电路输入引脚错位,输出结果全部取反。后续根据样例测试知道问题后修正下标就解决了。
(4)改进建议:
现有不足:
- 正则表达式硬编码分散在Parser类中,正则规则没有统一管理,后期新增器件需要频繁修改正则代码;
- 目前仅支持无反馈组合电路,无法支持带回路反馈的时序电路;
- 错误提示文案只有英文固定提示,没有区分错误细分类型,无法定位具体是引脚顺序错误还是信号冲突;
- 子电路只能单层嵌套,暂时没有办法在子电路内部再嵌套子电路,同时多层嵌套拓扑也不兼容,有待改进。
可持续改进:
- 在模拟器中新增拓扑排序+环路检测算法,新增环路检测模块,支持时序电路,识别电路反馈回路增加时钟节点,可以适配后续时序电路的迭代,避免造成像作业三一样由于复用性低而重构的麻烦。
- 用错误码替代固定报错字符串,区分多输出源、无输出源、引脚顺序等不同类型原因导致的错误,从而精准输出错误位置和错误原因,便于调试。
- 修改子电路实例化递归逻辑,用递归方式来展开多层嵌套子电路,应该可以解决子电路只能单层嵌套的问题。
(5)总结:
此次题目集初看难度很大,但第一次的难度主要在题目的理解上,如果理解了题目那么代码实现并不难,但第一次作业的重点不在实现而在设计。作为后面两次作业的基础,他最好满足SOLID原则以最大程度提高可复用性和可扩展性,前期良好的架构可以节省后期大量的重构,我第三次的作业重写就是血淋淋的教训。后续两次作业的难度有很大提升,涉及的知识不只局限于简单的面向对象程序设计基础,还有很多课本以外的知识,所以除了吸纳课上知识以外,还要多多学习别的知识。从最初的简单继承到后续一步步实现完善各种功能,最后完成了数据电路这么一个看似非常复杂实则也不简单的项目,我受益匪浅,也了解了程序设计与开发的工程意义。在完成这次作业的过程中,为了解题,我去了解了拓扑算法还有电路设计的一些知识,丰富了我个人的知识储备。
后续可以再深入学习图论拓扑排序算法作为课外拓展,同时我发现这次的代码还可以利用老师上课讲过的内容---工厂模式来进行改进,使用工厂模式统一管理门电路实现进一步解耦。
浙公网安备 33010602011771号