数字电路模拟程序总结性blog
一、前言
知识点
涉及到的知识点基本为
- java的基础语法以及类的基本构成,测试类,实体类,工具类的基本构成与作用的理解以及运用。
- 简单的设计原则,单一职责。认识到其具体实现以及初步感知其作用(便于后期扩展等作用)。
- java的基础特性:多态,通过父类引用操作子类对象;继承,将实体类的共性提取出来以便于写整体的行为,子类的行为,减少代码的重复;抽象类以及抽象方法的运用,更加规范更易于编程。
- 理解java的基础编程思想是面向对象而不是面向过程,面向对象会使后期更好修改优化,面向过程会写很多硬编程的地方,后期修改难度大。
- HashMap的存取历遍,HashSet的key的重复冲突
- 正则表达式,Pattern 与 Matcher,用于高效解析组件标识符字符串。
- 字符串的方法熟悉:split,replace,matches,toCharArray等。
- 接口,静态方法,组合模式的运用
- 用递归处理电路信号传递。
题量
题量相对较大,三次作业的量呈递增趋势,在前一次题目的基础上增加新的复杂元件,所以很考验底层的基础设计,实体类的增加,功能与特性的重叠度不同,设计越合理,完善起来越轻松。由于第一次题目的设计缺陷,太多面向过程编程,导致第二次题目为了方便并没有整体考虑,代码重复性高,需要重写的部分也多。第三次题目增加了子电路,意识到如果再面向过程编程,代码量会成倍增加,太冗余,也太耗时间,于是先修改优化底层设计,再进行扩展。所以说题量取决于底层设计以及扩展方式。
难度
难度相对较大,三此题目呈现递增趋势,从简单的基础元件到复杂元件再到子电路,非常考验底层设计。但是在前一次作业的基础上又不至于无从下手,只是写第二次题目时会发现第一次设计太过简单粗暴以至于很难优化修改,第三次题目集遇到的第一个难关就是哪里要提取出来哪里要设计成单独的类。但是没有涉及到算法,实现难度也只在正则的熟练运用,加了hashmap会更加方便,因此还是能够完成。
二、设计与分析
第一次题目的设计与分析
题目要求
【本次作业需求说明】
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
电路元件
电路中包含与门、或门、非门、异或门、同或门五种元件。
程序输出
按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件
类图

各个类详细设计
- change接口:顶层规范变换信号行为,所有元件都要实现该方法,根据自身逻辑计算输出信号。
- ecomponent类:元件顶层父类,封装公用属性,方法。
- 五个元件类(ANOXY):继承父类,实现change,实现五种不同输入输出逻辑
- ecomponentlist类:容器基类,所有元件列表的父容器,基于 ArrayList实现元件集合管理。
- 分类子容器类:继承 ecomponentlist,每种逻辑门对应一个独立列表(后续删去,在添加以及使用时加上强转可以实现相同效果)。
源码设计与分析
Source Monitor分析结果:
分析
由Source Monitor分析的结果可知,此次代码有以下三个问题:
一、注释偏低
注释率只有14.9%,只有关键部分的注释,后续应该做到对复杂逻辑进行注释,对功能模块进行注释,以便后续修改,优化。但是相比于第一次题目集,注释率大大增加,也明显发现有注释的好处,后续会一直改进,提升到20%+。
二、局部代码块嵌套过深(最大深度 = 6)
存在 6 层嵌套的代码块,多层嵌套会让代码可读性差,难调试,逻辑跳转复杂,结果不对时需要测试多个分支,花费时间长;容易遗漏边界情况,或者出现匹配不正确。可以将逻辑单独抽出变成方法,重构整体逻辑,使得代码简洁,可读性更高,更加易于调试以及减少出错;也可以提前return /continue,减少 else 嵌套。
三、圈复杂度高
changestate() 方法的圈复杂度达到 12,超过了业界公认的10的标准。方法内分支、循环过多,调试难度大,确定具体出错部分难,逻辑复杂、可读性差,后续维护极易引入 Bug,出错概率大幅提升。可以用用多态 / 策略模式替代大量 if/else,增加调用,减少大块的逻辑;把 if/else、switch 中不同分支的逻辑,拆成独立的小方法。
心得
通过类图可以发现各个类之间的关系太复杂,类也太多,回看源码会发现有许多重复代码,以及相似程度极高的类。足以看出设计并不合理,在写第二次作业时也验证了这个问题,类间耦合度高,不易于扩展与修改。后续应该加强对类的设计的学习,对设计模式的熟悉与应用。
Source Monitor分析可得,代码的嵌套深度太深,不易于调试,圈复杂度高,逻辑复杂,可读性差。可以通过合理设计以及适当拆取成小方法,多用多态,策略模式等减少重复代码,减少一个方法中的代码量,也更易于阅读,调试。
第二次题目的设计与分析
题目要求
在原有基础上增加三态门、译码器、数据选择器、数据分配器,增加额外输出要求,按照与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。 #如果某个元件的引脚没有接有效输入、输入输出之间断开(如三态门)或控制引脚输入无效,元件输出无效,程序输出忽略该元件。
#译码器不输出引脚电平,输出其输出为0的引脚的编号。如“M(3)1:3”代表译码器M3的输出引脚Y3输出0,其他引脚输出1。
#数据分配器按引脚编号从小到大的顺序输出所有输出引脚的信号,无效状态引脚输出“-”。
如“F(2)1:--0-”代表分配器F1的输出引脚W2输出0信号,其他三个引脚为无效状态。
类图

各个类详细设计
- ecomponent:所有元器件顶层父类,定义通用属性与方法。
- pecomponent:带输出引脚的无源元件父类,,继承 ecomponent,新增输出属性。
- 各类门电路 (A/O/N/X/Y):继承 pecomponent 并实现状态切换接口(基于第一次作业,无改动,尽量做到对扩展开放,对修改关闭)。
- 存储类元件 (S/M/Z/F):直接继承 ecomponent 并实现状态切换接口。
- change 接口:统一约束状态更新行为。
- pow 工具类:提供 2 的幂次计算,更易于计算引脚。
- ecomponentlist:元器件容器,管理同类型元件集合、查询、排序。
源码设计与分析

分析心得
分析
由Source Monitor分析的结果可知,此次代码有以下六个问题:
一、注释率过低
相比于第一次的作业,注释率占比低,没有说明,后续的可维护性低,难以进行修改优化,未说明边界条件。可能是类的相似度过高,并未另外添加注释。但是也间接证明了设计不合理,重复度高。后续需要给每个类以及重要方法加上职责注释,在核心复杂部分加上注释,易于后续调试,至少要将注释提升到15%到20%。
二、代码块最大嵌套深度太高
代码块的最大嵌套深度达到8层,平均代码块深度达到4.4,整体的编码普遍较深。第一是solve方法中的巨型switch加if/else在家for,整体逻辑全部一次性实现,实际可以拆分到类中,switch只进行简单调用,可以简化,在方法jiexi()中也同样如此。在第三次作业中我将每个元件的信息解析传递拆分到对应元件中,更符合单一职责,也在一定程度上降低了圈复杂度。
嵌套过深带来的问题
1. 可读性极差
代码呈 “向右箭头”,阅读需要不断横向滚动,逻辑层级一眼看不清。
2. 极易写错括号
层数多,{} 配对困难,漏写 / 多写括号会直接编译报错、逻辑错乱。
3. 调试困难
断点层级太深,单步跟踪要反复进入多层代码块。
4. 维护扩展成本高
新增组件(新增 case)、修改规则时,要在最深层改代码,牵一发而动全身。
5. 圈复杂度同步升高
嵌套 if/switch 越多,独立逻辑路径越多,测试用例成倍增加。
三、分支语句占比高
分支语句数值高达30.8%,if/else,switch,for,while等语句占比偏高,说明代码中条件判断和循环非常多,逻辑分支复杂。分支多,程序出口以及可能性多,不易于调试,测试点多,而且出了问题调试难度大。可以用状态模式 / 策略模式替代大量 if/else 或 switch,将分支占比的比例控制在20%以下比较理想。
四、类的平均方法过多
平均每个类有将近20个方法,出现上帝类,而且易于出现一个类承担多个功能,容易违反单一职责原则,不易于维护和修改。应该将类进行拆分,尽量保证单一职责原则,也易于后续修改
五、每个方低法的平均语句数过低
类多,空参构造,getter,setter方法多,就会使得只有一个语句的方法多,导致数值低。主要点在逻辑拆分极其细碎,功能分散,大量return提前终止,使得数值低。会让代码冗余,大部分无意义代码,说明拆分,设计并不合理。大量的微型方法会使得阅读代码时跳转频繁,可读性低,调试难度大。而且方法过多,命名,修改等也会出现问题,难排查问题以及漏洞。可以再好好设计底层逻辑,合并代码重复多的类,重构重复短逻辑方法,抽取公共方法,类似于可以将A/O/N/X/Y 的 checkstate()逻辑几乎相同,可以尝试统一实现。
六、圈复杂度高
最大圈复杂度达到8,由分析可得是在changestat()类中,此类本就是用于状态改变,信号传递,所以if/else的分支会多,可以尝试将状态转换单独提取出,每个类都单独实现,然后changestat()再调用,减少一部分if/else。提取重复的状态判断,赋值为独立的私有方法。
心得
在写第二次代码时,很容易发现第一次代码的缺陷,太过于面向过程编程,很多硬编程的地方,导致很难进行扩展,为了实现对修改关闭对扩展开放,增加了一个基础元件父类,但是也导致新增元件的代码重复率很高,类多且杂,类间关系更是复杂。并未想着对重复率高的代码进行提取,变成方法或类,方法未分配好,实体类只实现了基本属性以及简单方法,未实现什么业务逻辑,对于代码优化等没起很大作用。导致代码的圈复杂度高,嵌套深度高,在第三次作业时这部分花了大量时间进行梳理调整。写这次作业时,也会意识到第一次设计的不合理,意识到顶层设计的重要性。运用好设计模式非常重要,可以简化代码,也可以提升代码质量,是很好的解决问题的方法。不过通过此次练习,可以自己写出复杂一点的代码,也算是有成就感,一直在调试信号传导以及输入输出,还是挺有挑战性的。
第三次题目的设计与分析
题目要求
在题目一的基础上新增了子电路
子电路信息(本题在数字电路模拟程序-1基础上新增的内容)
可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。
子电路连接信息:(格式与主电路输入信息相同),子电路的元件编号与主电路中的编号可能相同。
子电路结束标志:endc
输入、输出信息的编码可以作为子电路的引脚号在主电路中引用。
子电路元件输出时带上子电路的编号,格式为:子电路编号+“-”+元件编号+“-”+引脚号+“:”+引脚输出
类图

各个类详细设计
- change 接口:统一约束所有组件的状态更新行为,实现 “所有组件都可以被统一调度”。
- ecomponent :抽象基类(所有组件的顶层父类)定义所有组件共有的属性和行为。
- pecomponent:抽象基类(带输出引脚的组件)为带单一输出引脚的门电路提供通用实现,减少重复代码。
- 多个基础电路元件的实体类:实现自己独有的检查信号,传递信号,输出逻辑,简化后面的业务逻辑,降低圈复杂度以及单个方法的代码量。
- Composite(子电路):继承:ecomponent,嵌套调用其他组件,实现组合模式,有自己单独的添加外部输入输出信号的方法以及解析逻辑,但后面发现似乎与主电路的相差不大,不过没想好如何更改。
- ecomponentlist 类(组件容器):统一管理同类型组件的集合,提供按 ID 查找功能。
- Solve 类:工具类,封装主电路和子电路的状态更新调度逻辑,处理连接关系。各元件也有单独的信号改变逻辑,放在这个类中统一实现解析信号。
- Jiexi 类:解析信号字符串,获取组件输出值。每种元件有对应的解析信号逻辑,放在这个类中统一实现匹配,识别信号。
源码设计与分析

分析心得
分析
由Source Monitor分析的结果可知,此次代码有以下个问题:
一、注释率低
注释行占比:6.3%,整个代码几乎没有业务注释、类注释、方法注释,所有类、抽象方法、复杂逻辑(位运算、电路解析、连线规则)都无说明,并不方便于后续修改,他人阅读。出现问题后,无法快速判断代码设计意图,排错缓慢。后续应该为所有的类添加注释,写明基本的功能以及信息,复杂方法应该加上说明,说清楚业务规则。
二、圈复杂度极高
最高圈复杂度:8,高复杂度方法,分支多、逻辑绕,集中在Composite.solve() / Composite.jiexi()等方法中而且仍然有大量的for循环以及switch语句,分支多,难阅读,难调试。而且代码健壮度差,容易出现问题,但是难以找出具体问题,要检查的测试点非常多。可以把格式校验、解析、业务处理逻辑解耦,分离到不同方法中,再按功能拆分大方法:把一个多分支的大方法,拆分为多个功能单一的小方法,这样代码的逻辑会更加清晰。
三、代码嵌套深度略高
最大块嵌套深度:7,主要在:checkExceptions、Composite.solve、Solve.solve。代码缩进极深,可读性大幅下降,在扩展修改时花费更多时间去阅读代码,理解代码,而且多层嵌套下,循环索引、变量作用域容易出错,引发数组越界、逻辑错乱,找出问题的难度也大大增大,调试时需要逐层进入代码块,排错效率极低。可以尝试将内层循环、判断逻辑抽成独立方法,过于复杂的代码块抽取为独立的方法,可以相对降低复杂度,优先使用提前返回,减少if的嵌套。
四、平均每类方法数过高
平均每类方法数为18.76,大于标准建议10,是因为类内部的方法过多,类职责臃肿。最大的问题出在Composite子电路类。这个类包含了成员变量,多个重载构造,集合读写,还单独实现了信号传导,解析,电路调度,输出打印等多个功能,导致方法数量太多,工具类solve。类内方法过多,阅读与维护1成本高。无法单独复用某一部分,代码的复用性差。而且不符合单一职责原则。后续需要修改Composite子电路类,把解析逻辑迁移至专门的解析工具类,打印逻辑抽离为输出工具类。让每个类只负责一类功能,控制单类方法数量在 10 个以内。
五、平均每方法语句数低
平均每方法语句数只有0.5,原因是类多,gettter,setter方法多,只有一行语句的方法多,而且类多,写了大量空参无语句构造方法。工具类中的独立小解析或者信号传导方法多,语句少,会拉低平均方法语句数。会带来逻辑1连贯性差,在修改,阅读代码时所花费的时间长,需要频繁跳转,调试代码困难而且会有代码冗余。可以通过整合零散工具方法:把功能高度关联、代码极少的 handleXxx/parseXxx 小方法,按业务逻辑合并为中型方法,将单个方法代码行数在 5~20 行区间。
六、平均块深度偏高
平均块深度:2.74,稍大于建议深度12,整体代码普遍嵌套偏深,绝大多数业务逻辑都在多层块内,整体编码习惯导致,全局平均嵌套深度超标,代码可读性差,调试难度大,后期优化扩展难度大。统一整改难度越来越大,形成不良编码习惯延续到新代码中,会导致越来越多的问题。应该改变一下编码习惯,少用高嵌套方法,写代码时也多考虑设计以及简化业务逻辑。
心得
写这次作业的第一个步骤就是将第二次作业中的solve,jiexi 两个类进行优化提取。因为发现子电路的逻辑与主电路相同,但是不能够简单的复制粘贴,这样代码中会出现非常多的重复冗余代码,于是先进行改造,提取方法。果然提取完后感觉代码的整体结构都清晰了,方法内的语句数也大量减少,逻辑更加清晰,就是在阅读时跳转会比较多。所以在优化修改代码时要权衡圈复杂度以及平均方法语句数,不能抽取过多的细碎方法,也不能将所有的业务逻辑全部写在一个方法中。代码的平均嵌套深度高,嵌套深度大,说明整体的编码习惯并不好,习惯了直接对于过程编码,对于事件进行编程,就容易导致这样的问题,后期需要加以约束,以防出现重大问题,而且平均嵌套过深确实导致了我的调试难度大,出现问题需要在多个出口判断,在写题目的过程中,最终调试花的时间相当长。写完第三次作业会明显感受到设计的重要性,后续可以尝试先画类图,进行功能设计后再进行编码,也应该多熟悉熟悉设计模式,出现问题时困难会是很好的解决问题的方法。
此次作业存在的问题
第三次作业存在多个测试点没通过的情况,只拿到了84分,对此我认为有多方面原因。第一是没有先进行设计再进行代码编写,导致整体的编码情况复杂,难调试,难找出问题。对于子电路的业务逻辑只是单纯复用主电路的,更加容易出问题,也导致代码太多太多,难以下手找出问题。第二点是switch,if/else的分支语句写的太多,导致代码出现问题时非常难排查具体问题,在进行提交代码时出现了两处非零返回的情况,最后也没有成功解决,边界情况难以考虑,程序出口又多,测试数据单凭自己也很难覆盖全面。第三点,太过于面向过程编码,只按照自己捋顺的电路逻辑来简单处理信号传导,以及解析等情况,非常容易出现逻辑漏洞,应该根据实体类的基础特性进行编码,会更加严谨,出错时也更能找到问题。第四点是类间的耦合度过高,修改时需要更改大量代码,会让整体花费时间偏高,导致无法按时完成此次作业。
三、踩坑心得
1.类的设计非常重要
在编写代码之前,应该先写一个基础的类的设计,画一个基础的类图,明晰各个类的功能分配以及类间关系,以防出现耦合度高,后期不好修改扩展的情况。也可以整体查看设计是否合理,重合部分是否多,可以在还没有编写代码的时候提取公共方法,相较于写完后再提取,可以大大减少时间,业务逻辑的实现以及代码实现也会更加简单,会少很多不必要的部分。三次迭代作业,每一次都深刻感知到设计合理的重要性,不然每一次作业的工程量都会爆炸,修改完前一次的代码再开始新的扩展,时间很容易不够,也会花更多不必要的时间。
2.实体类的设计需要全面且合理
在复杂的题目中,实体类不能只放简单的属性,getter/setter方法,可以根据特性,适当添加特有方法,可以简化后续的代码编写。可以在设计阶段,将可能会用到的方法进行功能划分,不让工具类中的方法太冗长。
3.单一职责的好处
在方法的功能单一且独立的情况下,复用就变得简单,也可以减少部分代码的编写。第三次1经过结构化处理部分方法后,主体业务逻辑减少了大部分的代码块,有的只是简单调用。使得代码更加易于读懂,修改,更容易定位代码的错误,更易于优化。
4.代码嵌套过深需要注意代码编写习惯
习惯于面向过程编码,当情况出现分支,或者有边界情况时,习惯于用if/else去补漏洞。用简单的逻辑去实现整个过程,在工程量不大的情况下还是可以实行的。但是一旦情况变得更加复杂,边界情况多,很难考虑全面,会出现很多不能可控的情况,而且在实际运行,有测试用例的情况下才可能被发现,代码的健壮度低。对于此次题目,可以尝试实现底层电路逻辑信号的传导以及改变,但难度确实很大,因此只做到了对现有测试用例的通过,用较为简单粗暴的方法解决问题。后续可以多熟悉设计模式,写出更好的代码,用更简单的解决方式。
5.圈复杂度过高
圈复杂度过高,会使结果有多个分支情况,会增加测试难度。在修改,过测试用例的时候,圈复杂度高带来的问题非常明显。我需要在多个位置去放置输出信号,来判断到底是哪里出了问题,错误点1难以定位。而且在下次打开代码,重新进行优化,测试的时候,阅读代码的时间也很长,可能性太多,需要大量的测试用例去测试我的代码漏洞。后续应该特别注意这个问题。
6.注释率仍低于标准
注释率低会带来代码的可读性差,这次的第一次作业1有在认真写注释,但是后面两词的注释就很少,可能是因为都是电路元件,重复的部分太多,已经非常熟悉,所以并未特别注意这个问题,但是仍需要在关键业务逻辑处加上注释。以后写代码要很注意这个问题,因为代码可能会面临着长时间以后的修改又或者是更加复杂的业务逻辑,所以需要保持良好的习惯。
7.大量的类,以及拆分细碎的方法会让方法的平均语句数降低
方法的平均语句数低,会让代码变得冗余,在阅读代码时会出现大量跳转逻辑,阅读难度增大,测试点多,不易于调试,容易出现漏洞。
平均语句数低
- 成因
1. 盲目追求功能单一,我们要做到的应该是职责单一,而不应该盲目抽取,解耦。
2. 提取无意义抽取公共方法。
3. 为了复用而复用:复用地方不多却偏偏要将简单方法抽取出来,只会增加代码冗余,以及不清晰。
- 解决方法
1. 按业务逻辑合并,而非强行堆代码:把同一业务步骤、连续执行、强上下文依赖的多层短调用,合并为一个完整方法,而不是非要拆分成单独的方法。
2. 规范方法抽取原则,不抽取无意义的公共代码。
3. 消除完全只做调用而且语句简单的方法。
四、改进建议
完成对于整体类的设计后再进行编写
直接写代码会出现很多问题,类的设计冗余,不合理,不符合单一职责原则。不清楚类间关系,不知道哪里需要用公共方法,实现业务逻辑的方法也不知该放在那里。如果先进行整体设计,可以在早期发现问题,在早期进行修改,也能够减少大量逻辑相同的冗余代码,减少编码时间。完成顶层设计后也易于看出业务逻辑设计是否合理,是否符合题目要求。
注释比例要提高
在写第一次作业时的注释率还不算太低,第二次编写时就轻松很多,很容易找到自己所需要的部分。而第二次第三次作业的注释率就非常低,但是大致无影响,这只是因为此次题目特点。但后续在编写代码时还是应该注意注释,保证代码的可读性,易于修改优化。
减少代码中的嵌套深度
在此次的作业集中,由于业务流程相对复杂,在代码中大量使用switch,if/else语句,导致分支情况多,非常难调试,测试漏洞,定位错误,因此在调试部分花费大量时间,测试用例也很难覆盖完全,出现的可能性太多,后续应该注意这个问题。
需要注意方法的平均语句数以及圈复杂度之间的权衡
编写的代码既不能够出现将全部业务逻辑放在一个方法中的情况,出现1上帝类,不易于后续的复用,修改,以及扩展。也要注意不要拆分的太过于细碎,出现太多的低语句方法,在阅读代码时会出现大量的逻辑跳转,也会带来问题难定位,难阅读的问题。后续需要改进优化。
适当提取公共方法
在代码编写完成后,如果出现了有许多方法的业务逻辑相同,可以适当提取公共方法。比如说在解析信号时,几个元件的解析信号逻辑相同,只有匹配输入信息不同,可以将匹配抽取成公共方法,在不同的类中通过不同的partten进行匹配,可以减少大量冗余代码,使得代码更加清晰简单。也易于修改,在整体逻辑改变之后,只需要修改一处,而不是每个部分都进行改变,会大大减少错误率。
五、总结与建议
总结
通过此次题目集,增强了编写代码的能力,可以独立解析解决相对复杂的问题。第二次第三次题目,让我逐渐意识到设计的重要性,它决定着后续修改,扩展所需要的时间以及精力。在编写代码时,尽量朝着一个合理的基础去扩展,编写,不然就会出现写第三次题目集的情况。在写新的问题之前,需要对先前的代码进行大量改动以及优化。这次题目集也让我掌握了hashmap,hashset的基本构成以及简单运用,锻炼了使用正则表达式的能力。通过类图以及代码的分析,也让我意识到自己不太好的编程习惯,注释写的少,而且习惯用if/else去补充漏洞,后续应该将逻辑写的更加严密,不要太过于面向过程编码,只会让修改难度增大。熟悉了继承,多态的基本运用,确实可以大大简化代码,让代码的逻辑更加清晰。
建议
关键测试点希望特殊表明,或者给一些简单的设计提示,现有能力不太支持复杂逻辑的设计,会觉得题目难度大,实现难。

浙公网安备 33010602011771号