作业集4~6总结性Blog

前言
这次连续三次作业都是数字电路模拟程序,三道题是递进关系,作业 4 是基础版本,只实现五种基础逻辑门;作业 5 在基础上新增了三态门、译码器、数据选择器、分配器四种复合器件,难度直接升到困难;作业 6 又加入了子电路复用、输入语法异常检测,整体代码量和逻辑复杂度又翻了一倍。三次作业全部用 Java 完成,下面简单梳理下三次作业涉及的知识点、题量和自己做题时感受到的难度。

作业 4(数字电路模拟程序 - 1)
(1)知识点:主要练 Java 面向对象继承和多态,写了抽象父类 Element,然后派生与、或、非、异或、同或五个门;还要自己手写字符串解析逻辑,拆分INPUT全局输入、带[]的引脚连线语句;用 HashMap 存元件和引脚电平,循环迭代更新信号直到不再变化;最后要把元件按类型、编号排序输出。
(2)代码体量:一共 8 个类,有效代码大概 235 行,主函数里堆了不少业务代码,没拆分工具类。
(3)难度感受:较难。最难的地方是自定义输入格式没有现成处理方法,所有分割、提取元件名、引脚号都要自己写判断,一开始经常解析错多位数编号的元件。
作业 5(数字电路模拟程序 - 2)
(1)新增知识点:在原有门电路基础上拓展四种复合数字器件,新增控制引脚、多路输出引脚的管理逻辑;修改元件解析代码,兼容译码器、选择器这类带控制引脚数量标识的元件;重写输出打印部分,新增对应器件的存储列表,严格按题目规定顺序输出;新增无效高阻电平标识,过滤没有完整输入的元件。
(2)代码体量:在作业 4 基础上新增 4 个器件类,总代码 477 多行,判断分支变多,代码重复度明显上升。
(3)难度感受:困难。踩了很多前期设计的坑,作业 4 的父类写死只有一个输出引脚,译码器、分配器多路输出只能在子类里额外加容器存储,多了大量 if 判断;不同器件引脚规则不一样,解析代码分支越写越长,很容易写错控制电平判断条件。
作业 6(数字电路模拟程序 - 3)
(1)新增知识点:本次改动最大,直接重构了底层代码,新增子电路容器类实现命名空间隔离,主电路和子电路元件互不冲突;写了五级优先级的语法错误检测,连线语句不符合规则要按优先级输出报错;分层仿真逻辑,先跑子电路再跑主电路,增加电平缓存和循环上限防止死循环;把解析、打印逻辑拆分成独立工具类,主函数变得简洁。
(2)代码体量:完整重构底层 Gate 父类,新增子电路、解析器、打印器等多个类,总代码 727 行左右,递归、多层判断特别多。
(3)难度感受:极高。子电路互相嵌套仿真逻辑绕得很,递归取值经常出现缓存失效;异常校验优先级很严格,一条连线同时触发多种错误时只能输出最高优先级报错,调试了很久;还要处理主、子电路同名元件互相干扰的问题。

整体总结
三次作业一环扣一环,前面代码写得乱、抽象不到位,后面迭代就要大规模改代码。作业 4 只是完成基础功能,没考虑扩展性,导致作业 5、6 都要反复重构底层结构。整体题量一次比一次大,知识点层层叠加,对面向对象、字符串处理、仿真逻辑、异常处理都有完整的训练。

设计与分析
下面结合我自己写的三份 Java 代码,搭配 SourceMonitor 统计的代码数据和 PowerDesigner 画的简易类图,分别分析三次作业的代码结构,顺带写一下自己写代码时的想法和体会。

基础指标说明
SourceMonitor 统计指标:有效代码行(去掉空行、注释)、类和方法数量、平均圈复杂度,圈复杂度越高说明 if/for 分支越多,代码越难读;
类图分层:分为数据实体类、门器件实现类、工具业务类、程序入口 Main 四层。
(一)作业 4 代码设计分析

  1. 代码度量数据
    SourceMonitor截图:

1

类图:

tongyi-mermaid-2026-06-23-160957

  1. 类结构解读
    Pin 类:纯数据类,只存元件名和引脚编号,重写 toString 方便打印引脚格式,没有复杂逻辑。
    抽象父类 Element:当时想着把所有门通用的功能放这里,统一管理输入引脚、引脚电平映射,写了抽象方法 computeOutput,让五个门各自实现自己的逻辑。通用方法包含设置引脚值、检查输入是否齐全、格式化输出结果。
    AndGate、OrGate 等五个子类:只重写计算输出的方法,构造方法调用父类初始化,没有额外属性,遵循单一职责,只负责电平运算。
    Main 主类:这部分是我写得最乱的地方,文本读取、解析 INPUT、解析连线、循环仿真、分类输出全部堆在 main 里,没有拆分方法,后期读代码很费劲。写了两个工具方法 parsePin 拆分引脚字符串、createElement 根据元件名创建对应门对象。
  2. 个人设计心得
    当时写作业 4 只想着先把功能跑通,抽象父类的设计还算省心,新增门电路不用改上层代码。但有两个地方考虑不周,给后面埋坑:一是直接写死输出引脚固定为 0,完全没考虑之后会有多路输出的器件;二是所有业务逻辑全塞在 Main 里,耦合严重,后面加新功能只能不停往 main 里堆代码;还有没有记录引脚信号来源,没办法校验 “一个输入引脚不能接多个输出” 这个约束条件。
    (二)作业 5 代码设计分析
  3. 代码度量数据
    SourceMonitor截图:

2

类图:

image_863001832850763

  1. 类结构改动
    保留原来 Element 继承体系,新增四种复合器件继承 Element,在子类内部额外添加集合存储多路输出和控制引脚;
    单独抽了 printResult 静态方法,新建九个列表分别存九类器件,遍历全部元件完成分类,再按编号排序依次打印;
    修改 createElement 解析方法,新增 S/M/Z/F 标识符的判断逻辑,截取括号内的数字获取控制引脚、输入引脚数量;
    用 null 标记无效高阻输出,打印时跳过没有完整输入的元件。
  2. 个人设计心得
    这次迭代明显感受到前期设计不足的代价,父类只适配单输出门,译码器这类多路输出器件只能在子类里写一堆特殊判断,代码可读性很差。好在继承多态还能用,原来五个基础门的代码完全不用改动,只需要新增子类就能实现功能。缺点就是解析和仿真逻辑还是全在 Main 里,代码越来越臃肿,很多重复的遍历排序代码,当时懒得封装工具方法,能复制粘贴就直接复制了。
    (三)作业 6 代码设计分析(完全重构版本)
  3. 代码度量数据
    SourceMonitor截图:

3

类图:

tongyi-mermaid-2026-06-23-162342

  1. 全新类结构说明
    Gate 通用父类:直接废弃之前的 Element,重新写了适配所有九种器件的父类,不再限制单输出,统一存储输入端口、当前输出、上一轮输出状态,提供提取元件编号的通用方法;
    CircuitLink 连线类:单纯保存一条连线的源引脚和目标引脚,方便统一遍历、冲突检测;
    SubCircuitUnit 子电路容器:本次核心新增类,每个子电路单独存储输入输出端口、内部连线、独立元件集合,和主电路完全隔离,解决同名元件互相干扰的问题;
    工具分层:CircuitDataReader 专门处理文本读取、分段解析、错误校验、仿真运行;ResultPrinter 单独负责分类排序、打印结果;Main 主函数只做初始化和调用,逻辑干净很多。
  2. 个人设计心得
    这次重构是三份代码里最规范的一版,把数据存储、器件运算、输入解析、结果打印分开,不用再挤在一个主函数里。子电路独立命名空间的设计解决了编号冲突的大问题,异常校验单独写在解析方法里,不用改动仿真核心代码。不过也有遗留问题,引脚取值靠不停截取字符串匹配,没有写专门的分词工具,如果输入文本很长或者格式很乱,解析速度会很慢;多层子电路嵌套时递归太深,代码读起来很绕,调试的时候花了不少时间。

采坑心得
这部分都是我写代码过程里实实在在遇到的 bug,结合测试用例、代码报错、运行结果来说,不写空话,记录当时踩的坑和最后怎么改好的。

作业 4 踩坑记录
坑 1:元件编号只能解析个位数,A (8) 10 这类多位数识别失败
最开始截取数字只用了单字符判断,碰到两位及以上的编号就会拆分错误,元件缺失输入,直接不输出结果。
解决:改成循环读取末尾所有连续数字字符,支持任意长度数字编号。
坑 2:信号循环没有上限,复杂电路循环几十次
只用 while (changed) 循环更新电平,没有最大次数限制,层级多的电路会反复遍历所有连线,运行卡顿,后面作业 6 借鉴这个问题加了 MAX_LOOP 限制。
作业 5 踩坑记录
坑 1:译码器控制条件写反,全部测试用例输出错误
题目要求译码器 S1=1、S2+S3=0 才正常工作,我写代码的时候不小心写成 S1==0,所有输出全部颠倒。
当时调试花了快两小时,译码器内部循环嵌套多,找逻辑错误很难。最后单独把译码器是否开启抽成一个布尔变量,先判断开关状态再计算输出,分开两段逻辑,不容易写错。
坑 2:三态门高阻和低电平 0 区分不开
一开始高阻浮空直接赋值 0,打印的时候没法过滤无效元件,输出列表混着很多无意义结果。后面用 null 代表无效电平,打印的时候判断如果输出是 null 就跳过不输出。
作业 6 踩坑记录
坑 1:异常判断顺序不对,不符合题目优先级要求
题目规定错误优先级从高到低:无输出引脚 > 输入输出顺序颠倒 > 无输入引脚 > 多个输入引脚 > 引脚信号冲突。我最开始 if 判断顺序写反了,一条连线同时触发两种错误时,输出的报错不是最高优先级的。
修复:按题目优先级从上到下依次判断,匹配到第一个错误就直接返回,不再执行后面的校验。
坑 2:子电路循环引用,程序无限递归卡死
测试用例里 C1 输出接 C2 输入,C2 输出又接回 C1,递归仿真没有终止条件,CPU 直接占满。
解决:设置常量 MAX_LOOP=100,每次仿真循环计数,超过上限直接跳出循环,避免死循环。
三次作业共同踩的通病
(1)前期图省事,不预留扩展接口,后面功能增加只能大规模重构,越往后改代码越痛苦;
(2)解析、仿真、打印逻辑耦合在一起,前期不会拆分工具类,全部堆在 Main,想单独测试某一段功能很难;
(3)测试只测简单正常输入,很少考虑多位数编号、空行、多层子电路、一条连线多个错误这类边界情况,每次都是交作业前才发现一堆隐藏 bug。

改进建议
结合自己写的代码和踩过的坑,站在学生学习的角度,说说这份代码后续可以优化的地方,方便之后继续完善程序。
一、代码结构优化
提取统一常量类,消除硬编码数字和字符串。
现在代码里到处都是 0、1、-1、A/O/N 这类标识,还有报错文字分散在各个方法。可以新建常量类,用枚举表示器件类型、电平状态、错误等级,所有固定文字、数字统一调用常量,减少拼写错误,后期修改只需要改一处。
二、仿真逻辑性能优化
多路输出器件计算逻辑参数化,减少重复代码。
现在每次仿真都遍历全部连线,很多元件引脚没有变化也会重复计算。可以解析完成后生成电路拓扑排序,仿真时只按依赖顺序更新变化的器件,不用全量遍历,电路规模大的时候能明显加快运行速度。
三、程序容错性优化
分级打印日志,区分致命错误、普通错误、警告。
输入预处理,过滤非法字符、多余空格。
代码现在没有处理全角符号、大量空行、多余空格,很容易解析报错。新增预处理步骤,把全角符号转半角、压缩连续空格、删除空白行,标准化文本后再解析。

综合总结
一、三次作业学到的内容
(1)夯实了 Java 面向对象基础,完整练习了继承、抽象、多态、封装。从最开始简单的父类派生,到后面分层容器、命名空间隔离,慢慢理解单一职责、开闭原则是什么意思,也切身体会到前期架构设计对后续开发的影响,前期偷懒简化代码,后面迭代要花几倍时间重构。
(2)掌握了多优先级异常处理、边界防护的写法,改掉了默认输入一定合法的思维,写代码时会主动考虑非法输入、循环死锁这类边界情况,程序鲁棒性比最开始好很多。
(3)学会用 SourceMonitor 简单统计代码指标,通过圈复杂度、代码行数判断自己代码哪里写得冗余、分支过多,养成写完代码复盘优化的习惯。
二、自身不足与后续学习方向
(1)仿真算法比较基础,只是简单循环迭代更新电平,没有接触事件驱动仿真,大型电路运行效率很低,后续了解数字仿真相关优化算法。
(2)只会控制台文本交互,没有图形界面,调试起来很麻烦。之后打算学一点 JavaFX/Swing,做一个简单可视化拖拽界面,直观操作电路、实时看引脚电平。
(3)没有做持久化存储,电路数据只存在内存里,关闭程序就丢失。之后学习 JSON 序列化,实现电路工程文件保存、读取,搭建简易元器件库。
三、整体学习感悟
这三次数字电路仿真作业相当于一次小型完整的软件迭代练习,从只能实现基础功能的初代版本,到扩展硬件器件的第二版,最后完善模块化、容错能力的工程版本,一步步复刻了软件更新迭代的流程。
写代码过程里最大的感受就是 “前期偷懒,后期受罪”,作业 4 为了快点写完简化父类设计、不拆分工具类,作业 5、6 只能不停重构底层代码,浪费了很多时间。同时也意识到写代码不只是实现功能,还要考虑可读性、扩展性、容错性,不能只追求程序跑通就交差。
这次作业把面向对象、字符串处理、逻辑仿真、异常校验结合在一起,既巩固了 Java 语法,又复习了数字电路硬件知识,一举两得。之后会针对自己代码暴露的短板慢慢补充学习,持续优化这份仿真程序,让代码更规范、功能更完善。

posted @ 2026-06-23 16:40  25201321-黄佳星  阅读(5)  评论(0)    收藏  举报