面向对象程序设计三次作业总结报告-第二期

面向对象程序设计三次作业总结报告-第二期

(1)前言

这三次作业做下来,最大的感受是:题目在迭代,我也在迭代——只不过它迭代得越来越复杂,我迭代得越来越慢。

第一次作业(数字电路模拟程序-1),实现与门、或门、非门、异或门、同或门五种基本门电路。知识点主要集中在类的继承体系、引脚状态管理和信号传播的循环模拟上。题量40个测试点,难度中等偏上,大概四星左右。第一次接触这种“门连门”的模拟,脑子里全是“与非或”,写完以为自己稳了,结果最后一个多层级联测试点非零返回,让我意识到:电路可以级联,但我的代码稳定性不能级联。

第二次作业(数字电路模拟程序-2),新增了三态门、译码器、数据选择器、数据分配器四种组合元件。难度直接拉到五星。最大的挑战不是逻辑多难,而是引脚编号规则太乱了——控制端、输入端、输出端排列组合,每一种元件都有自己的编号规则,解析连接信息的时候感觉自己在做解密游戏。34个测试点,错了3个,基本都是输出格式问题,说明我对题目的“理解”和系统对题目的“理解”之间存在偏差。

第三次作业(数字电路模拟程序-4),引入子电路和异常检测。这是最折磨人的一次,难度接近“满分星”。子电路要用组合模式设计,异常检测还要按优先级顺序输出,43个测试点包含大量异常场景。我花在debug上的时间比写代码还长,尤其是子电路输出缺前缀、异常优先级判断不对,反复改反复错,最后只能说“尽力了”。

三次作业一路做下来,从“我能搞定”到“好像没那么简单”到“我到底在写什么”,心态变化很真实。不过回过头看,每次踩坑都确实学到了东西——虽然过程不太体面,但结果还算值得。

(2)设计与分析:

第一次作业:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。请编程实现数字电路模拟程序。

下图是第这次作业的SourceMonitor生成的指标报表截图
fourtu

分析可得总代码行数 438行 适中
类/接口数 10个 粒度合理
每个类的方法数 5.0 比较均衡
平均每个方法语句数 4.36 大部分方法很简洁
最大圈复杂度 23 偏高, 指向parseConnection()
最大嵌套深度 8层 同样指向parseConnection()
平均复杂度 2.36 整体可控
报表里最扎眼的数据就是最大圈复杂度23和最大嵌套深度8,两者都指向DSystem.parseConnection()这个方法。

说白了就是,所有连接信息的解析逻辑全让我塞这一个方法里了——判断信号源是外部输入还是元件输出、遍历目标引脚、查找元件、赋值……一个方法干了一堆事,写着写着就变成了一锅粥。圈复杂度23意味着有23条不同执行路径,我压根没精力全部考虑到,所以case40非零返回估计就是某个我没覆盖到的边缘情况崩了。createGate()复杂度12,现在凑合能用,但我已经预感到后面作业加新元件的时候它会越来越臃肿。报表里也有看着舒心的数据:平均每个方法只有4.36条语句,AndGate.compute()就6行,Pin.hasValue()一行完事。五个门子类都挺干净的,各算各的逻辑。10个类的划分也还算合理,Simulator和Pin都单独拎出来了。总结就是:类结构还行,但parseConnection()是我自己埋的雷,迟早得拆。

下图是作业类图
four
这个类图之间的关系:
Gate → Pin:组合关系。AndGate/OrGate/NotGate/XorGate/XnorGate → Gate:继承关系。。DSystem → Gate:聚合关系。DSystem → Simulator:依赖关系。Main → DSystem:依赖关系。类。整体来看,DSystem和Simulator的关系有点拧巴,一个管数据一个管逻辑,分是分开了,但配合起来并不松散。

第二次作业:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,
数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。
这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序。

下图是第这次作业的SourceMonitor生成的指标报表截图
five图
代码行数从438涨到了841,翻了一倍,语句数也从299涨到了640,新增了四种元件的逻辑都在里面了。类从10个变成了14个,每个类的方法数从5.0略增到5.64,平均每个方法的语句数从4.36涨到了6.32。但真正让我有点慌的是复杂度。最大圈复杂度从23直接飙到了60,DSystem.parseConnection(),我承认我在里面又加了四个else if分支去处理三态门、译码器、选择器和分配器,每个元件的引脚规则都不一样,处理起来一堆判断。最大嵌套深度也从8层涨到了9+,找bug的时候一层层往里跳,跳得我头晕。平均复杂度从2.36涨到了3.39,说明不止那一个方法,整体代码都变得更复杂了。createGate()复杂度从12涨到了大概16左右,也是因为加了四种元件的创建逻辑。分支语句占比从20.1%涨到了24.5%,方法调用从138涨到了305,这两个数据也印证了逻辑判断和调用关系都在变多。报表里也有还行的数据,平均每个方法6.32条语句,虽然比第一次的4.36长了,但考虑到总代码翻倍,这个涨幅算正常。14个类各司其职,新增的元件都独立成类了,没有往一个类里硬塞。不过说实话,我写的时候已经意识到parseConnection()快撑不住了,但为了赶进度没来得及拆。(真的没招了,这能如此了)。

下图是作业类图
five
代码的类图关系:
Gate → Pin:组合关系。
Gate:继承关系。GateA、GateN、GateO、GateX、GateY。
GateS/GateM/GateZ/GateF → Pin:组合关系。
DSystem → Gate:聚合关系。
DSystem → Simulator:依赖关系。
Main → DSystem:依赖关系。
第二次作业的类继承体系从5个膨胀到9个。GateS、GateM、GateZ、GateF都挂上了,但每个新元件都有自己的引脚编号规则,GateM和GateF还有多个输出引脚,各自实现了getPinByNumber()和isOutputPin()。麻烦的是DSystem处理连接时得先判断类型再向下转型才能调用这些方法,parseConnection()里塞满了if(type.equals("M"))这种分支。每加一种元件就要改三四个地方,看着子类多了挺唬人,实际上就是让DSystem越来越臃肿。(没办法了,已经开始哭了)

第三次作业:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。本题目在“NCHUD-数字电路模拟程序-1”的基础上迭代,增加的功能参见题目中的标注。

下图是第这次作业的SourceMonitor生成的指标报表截图
six图
总代码行数:881行,语句数:725,分支语句占比:25.9%,类/接口数:11个,每个类的方法数:7.82,平均每个方法语句数:6.84,最大圈复杂度:36,最大嵌套深度:9+,平均复杂度:3.63。
最大嵌套深度9+基本没变,说明虽然类结构重构了,但深层嵌套的老毛病还在。该涨的都涨了——行数、复杂度、方法数、调用数。类倒是没怎么涨,说明我把更多功能塞进了现有类里,而不是拆出新类。两次迭代下来,代码规模翻倍,复杂度整体上移,但深层嵌套和parseConnection过长的老问题只是换了种形式存在,没真正解决。
下图是作业类图
six
Component → Pin:组合关系
Leaf → Component:继承关系
GateA/N/O/X/Y → Leaf:继承关系
Composite → Component:继承关系
Composite → DSystem:组合关系
DSystem → Component:聚合关系
Main → DSystem:依赖关系
第三次作业把继承体系换成了Component-Leaf-Composite三层结构,理论上是为了让子电路能像基本门一样被统一处理。但实际写下来,Composite内部要自己创建DSystem去跑子电路模拟,外面主DSystem又要管理一堆Composite实例,还加了个全局静态模板表用来复制子电路。两层DSystem互相套着,调试的时候经常搞不清引脚属于哪一层。Component和Leaf那部分还凑合,基本门跟原来差不多,但Composite为了处理子电路的输入输出映射、前缀传递、实例复制,比原来任何一个类都复杂。设计上想让一切统一,结果实际用起来反而更绕了。

(3)采坑心得

第一次作业
第一次作业跑了39个测试点全过,最后case40多层级联电路非零返回,最终得分95。查了半天发现是parseConnection()这个方法的锅,圈复杂度23,嵌套8层,所有解析逻辑全塞在一个方法里,某个边缘分支没覆盖到直接崩了。当时我还觉得自己设计得不错——抽象Gate、独立Pin、分离Simulator,结果被一个多层级联教做人了。调试的时候一层层往里跳,跳得我头晕。最后那5分没拿到,我心里清楚不是题目难,是我那个方法写得太长了,改都没法改。核心教训:一个方法真不能写太长,拆不开的东西迟早会炸。

第二次作业
第二次作业新增三态门、译码器、选择器、分配器四种元件,我直接在parseConnection()里加了四个else if分支,最大复杂度从23飙到60。34个测试点错了3个——case7基本元件测试输出顺序错了、case16译码器零引脚编号映射不对、case27复杂电路输出格式不符,最终得分91。最气的是这三个错的点全跟输出格式有关,逻辑本身没问题,但引脚号算错了、排序没排对、无效状态写成null没转成"-"。改的时候发现代码已经绕成一团了,动一个地方可能影响到完全不相干的元件。核心教训:靠加if-else堆功能的代价就是代码越来越脆,加一个功能要动一堆地方,一改就错。

第三次作业
第三次作业最崩溃。引入组合模式和子电路之后,输出缺前缀导致好几个case直接0分。最终得分83,比前两次都低。就像是神里绫华开了个大以为能一波清场,结果冰花全冻在空气上了——伤害再高打不中有什么用呢?最折磨的是有些错误我明明知道在哪,改完了又冒出新的错,改了好几天分数就是上不去。后来才意识到我的类设计本身就有问题,每次改动都在打补丁,从来没真正解决问题。这次让我明白了,设计模式是好东西,但半懂不懂地硬用比不用还糟糕。核心教训:重构之前先把原来那坨拆干净,不然新结构套旧问题,改起来又累又没用。

(4)改进建议:

第一次作业
parseConnection()这个方法被我写得太长了,里面什么解析逻辑都有,圈复杂度23。我后来想了想,完全可以把拆成几个小方法,parseSource()专门管信号源,parseTargets()专门处理目标引脚,每个方法只干一件事。拆完之后每个方法的复杂度应该能降到5以下,这样哪里出问题一眼就能看到,不用在两百多行代码里翻来翻去。当时写的时候没注意,现在回头看,拆开不仅好改,调试的时候也不用一层一层往里跳了。

第二次作业
加元件的方式有问题,我直接在parseConnection()里面堆else if,四种新元件加了四个分支,复杂度直接飙到60。其实应该让每个元件类自己管自己的引脚,DSystem统一调用gate.getPinByNumber()就行了,不用先判断类型再向下转型。这样加新元件就只需要新建一个子类,不用改DSystem里面任何代码。我当时为了省事全塞一起,结果改一个地方影响到别的地方,改完case7又炸case16,越改越乱。

第三次作业
子电路的复制和异常检测这块写得最乱。Composite.copyInstance()用的浅拷贝,多个实例共用一个连接列表,改一个全乱了,好几个测试点都是因为这个崩的。应该改成深拷贝,每个实例独立复制一份数据。还有异常检测的优先级逻辑也粘在DSystem里面,本来就应该单独抽一个类出来管这件事。Main.main()里面那段子电路解析也写得乱七八糟,拆出来放到SubCircuitParser里会清楚很多。我当时就是懒,觉得写在一起方便,结果后面改都不知道从哪下手。

三次作业做下来,最大的感受就是——分数在迭代,但迭代的方向是向下。

(5)总结

第一次还觉得自己设计得挺像回事,抽象Gate、独立Pin、分离Simulator,结果被一个parseConnection一锅端了,95分收场。第二次加元件的时候我已经感觉到不对劲了,但还是硬着头皮往parseConnection里塞else if,代码越来越脆,91分。第三次我甚至把继承体系都重构了,搬出了Component-Leaf-Composite这套组合模式,结果两层DSystem互相套,复制还搞了个浅拷贝,改了几天bug分数掉到83。越努力分数越低,也是没谁了。
不过回头想想,学到的东西其实不少。第一次让我明白了“一个方法不能写太长”这个道理,虽然实践起来还是控制不住。第二次让我意识到硬加功能而不重构的代价,代码膨胀起来比我想象的快得多。第三次则直接告诉我,设计模式这种东西半懂不懂地硬用,还不如老老实实拆方法,花里胡哨的架构救不了烂代码。当然,该补的东西还有很多。比如怎么写出真正低耦合的设计,让加新功能的时候不用改旧代码;比如深拷贝到底应该怎么实现,不是简单new一个就完事了;比如异常处理的优先级逻辑应该怎么组织才能既不乱又不漏。这些东西我现在脑子里还是一团浆糊,需要再多写几道题才能真正搞明白。
总之这三次作业教会我一件事:代码写得越长,问题就藏得越深。下次写代码我争取每写50行就停下来看一眼,该拆的拆该改的改,别等写到800多行的时候才发现自己埋了一堆雷。

posted @ 2026-06-24 20:38  水煮一只虾  阅读(5)  评论(0)    收藏  举报