308
社区成员
发帖
与我相关
我的任务
分享本文围绕 2026 年面向对象课程第三、第四单元,以及四个单元整体学习过程展开总结。第三单元重点讨论 JML、规格驱动开发、JUnit 测试、三次作业迭代和性能问题;第四单元重点讨论图书馆管理系统中的正向建模、两阶类图、UML 与代码的追踪关系,以及大模型辅助复杂架构设计的经验。最后总结自己在四个单元中架构设计思维、测试思维和课程认知的变化。
在 OO 课程的前两个单元中,我的主要工作方式仍然是“先把程序写出来,再通过样例和互测修补问题”。第一单元更多考察表达式对象的抽象、递归结构和化简逻辑;第二单元则把重点转向多线程电梯调度、生产者—消费者模式、同步控制和状态一致性。进入第三、第四单元后,课程重点发生了明显变化:
这也是我对 OO 课程后半段最大的理解:一个复杂系统不能只靠临时判断和局部修补维持正确性,必须有更稳定的上层约束。第三单元中的 JML 是行为层面的约束,第四单元中的 UML 类图和顺序图则是结构层面与交互层面的约束。前者告诉我“方法应该满足什么条件”,后者告诉我“系统中应该有哪些对象、对象之间如何协作”。
JML 最大的价值在于把自然语言中容易含混的需求转化为接近形式化的契约。对一个方法来说,JML 通常通过 requires 描述前置条件,通过 ensures 描述后置条件,通过 assignable 约束可修改范围,通过 signals 描述异常行为,通过不变量描述对象长期保持的性质。
我在第三单元中逐渐形成了一个理解:JML 不是普通注释,而是“客户—实现者之间的契约”。调用者只要满足前置条件,就有权期望实现者满足后置条件;实现者只要正确处理规格允许的状态,就不需要对规格之外的调用负责。这样一来,程序正确性的讨论就不再停留在“我觉得这样写应该对”,而是可以回到“该方法是否满足规格所描述的行为集合”。
在规格驱动开发中,我认为最关键的不是机械翻译 JML,而是理解 JML 背后的抽象状态。例如社交网络类作业中,Person、Relation、Tag、Message 等对象的具体容器可以不同,但外部可观察行为必须符合规格。也就是说,JML 关注的是抽象语义,而不是规定一定使用 ArrayList、HashMap 还是并查集。实现者需要在满足语义的前提下自行选择合适的数据结构。
我把第三单元的开发过程理解为三个层次:
| 层次 | 核心问题 | 我的体会 |
|---|---|---|
| 规格阅读层 | 这个方法到底承诺了什么? | 不能只看方法名,要看前置、后置、异常和可修改范围 |
| 抽象建模层 | 哪些状态必须被程序维护? | 需要找到对象之间的真实关系,例如人、关系、消息、标签之间的映射 |
| 实现优化层 | 怎样在不破坏规格的情况下提高效率? | 规格通常不限制容器,但数据规模会倒逼容器和算法升级 |
最初我容易把 JML 当成“高级版题面”,只关注输出对不对。后来我意识到 JML 更像是程序行为的边界定义。比如一个查询方法如果没有修改对象状态,那么 assignable \nothing 就是非常重要的约束;一个涉及旧状态和新状态比较的方法,则必须认真区分 \old(...) 和执行后的状态,否则很容易把“执行前已经存在”和“执行后新增”混在一起。
在实践中,我认为 JML 作业最容易出问题的地方主要有四类:
List 中允许重复,但规格中的集合语义可能不允许重复;如果容器选择和抽象语义不一致,就会引入隐蔽错误。\old 强调执行前状态,很多后置条件都依赖这种状态差异。如果实现中先修改再判断,就可能破坏逻辑。因此,规格驱动开发并不是“照着 JML 翻译代码”这么简单。更准确地说,它要求我们从规格中抽取抽象模型,再设计高效且可维护的实现模型。
第三单元中,JUnit 对我最大的帮助是把“临时手测”转化为“可重复执行的测试资产”。手动构造输入输出虽然直观,但每次修改程序后都需要重新运行、重新观察结果;JUnit 则可以把这些经验固化下来,使每次重构后都能快速确认基本行为是否被破坏。
我在 JUnit 测试中主要关注四类对象:
| 测试对象 | 典型内容 | 目的 |
|---|---|---|
| 构造与基础状态 | 添加人、添加关系、添加消息、添加标签 | 确认基础容器和初始化逻辑正确 |
| 正常业务路径 | 查询关系值、发送消息、计算联通块、修改标签 | 确认主流程符合规格 |
| 异常路径 | 重复 id、不存在 id、非法关系、非法消息 | 确认异常类型和触发顺序正确 |
| 性能相关路径 | 大量节点、大量关系、集中查询、重复查询 | 发现线性扫描、重复计算等瓶颈 |
我一开始更重视“样例是否通过”,后来逐渐意识到 JUnit 的价值不只是检查答案,而是为迭代提供安全网。尤其是在第三单元后两次作业中,新增方法会影响已有容器设计。如果没有针对已有方法的回归测试,修改一个容器很可能导致旧功能被破坏。
我的主要经验有以下几点:
JUnit 让我意识到,测试不是提交前的最后一步,而应该贯穿开发过程。尤其是规格驱动开发中,测试用例应当直接从 JML 中抽取,而不是只从自然语言题面中抽取。
第三单元三次作业的迭代过程可以概括为:
| 迭代阶段 | 主要变化 | 架构压力 |
|---|---|---|
| 第一次作业 | 建立基本对象与关系查询 | 需要快速建立 id 到对象的映射 |
| 第二次作业 | 增加标签、消息、复杂查询 | 关系和消息不再是简单线性结构,容器设计开始影响性能 |
| 第三次作业 | 增加更多统计与动态维护 | 需要在修改时维护缓存,而不是查询时全量重算 |
第一次作业中,如果所有方法都用数组或列表线性扫描,可能仍然能接受;但后续迭代中,查询次数和数据规模上来后,线性扫描会迅速成为瓶颈。因此,第三单元真正考察的不只是“能不能读懂 JML”,还考察“能不能从 JML 中预判未来迭代对数据结构的要求”。
我的方法是把每次新增规格按以下方式重新审视一遍:
Message 到底由网络统一管理,还是由发送者、接收者、标签共同管理,必须明确所有权。对容器的选择,我逐渐形成了一个原则:凡是通过 id 高频查找的对象,都应该有 HashMap<Integer, Object> 类型的索引;凡是需要动态维护集合关系的结构,都应该避免只用线性表;凡是查询结果可以在更新时维护,就不要在每次查询时从头计算。
我发现性能瓶颈主要依靠三种方式:
forall 或 exists,直接翻译通常意味着较高复杂度,需要思考是否可以用缓存或增量维护。这让我认识到,规格驱动开发不能忽视性能。规格给出了“做什么”,但没有直接给出“如何高效地做”。真正的实现需要在规格正确性和算法复杂度之间取得平衡。
第二次研讨课中的 JML“击鼓传花”给我的触动很大。这个活动表面上是在传递和修改 JML,实际上暴露的是多人协作开发中最典型的问题:每个人都以为自己理解了需求,但每个人理解的边界条件可能并不一样。
在传递过程中,我主要意识到以下几类 JML bug 或风险:
这说明 JML 的难点不只在语法,而在语义精确性。形式化语言能减少歧义,但前提是编写者真的把边界写完整。
我认为确实发生了变化。变化并不一定表现为“大需求被改掉”,更多时候是细节边界发生漂移。例如:
这些变化单独看都不大,但在多人协作中会累积成严重的信息差。一个人认为“显然不允许”,另一个人可能认为“规格没禁止就是允许”。最终结果就是同一份需求在不同人手中被实现成不同程序。
如果今后进行多人组队编程,我认为至少要采取以下措施:
这次研讨让我认识到,团队协作中最危险的不是“大家不会写代码”,而是“大家以为自己理解的是同一个需求”。
第四单元的主题是图书馆管理系统。相比前三个单元,第四单元的特殊之处在于它明确要求先进行 UML 建模,再完成代码实现,并且需要提交预设计类图和最终类图。这使我真正体验到了正向建模的作用。
我在本单元中的正向建模过程大致如下:
LibraryIO 或命令类负责,自己的程序只负责业务状态维护。Main 或单一管理类中。这个过程让我认识到,UML 不是作业提交的附属品,而是复杂系统开发前的结构草图。尤其在图书馆系统这种状态多、对象多、流程多的任务中,如果没有模型,后续迭代很容易变成局部补丁堆叠。
本单元的两阶类图可以理解为“预设计类图”和“最终类图”。它们的作用并不相同。
| 类图阶段 | 主要作用 | 我的理解 |
|---|---|---|
预设计类图 uml_pre.mdj | 在编码前确定对象划分和责任边界 | 重点是方向正确,避免一开始就写成过程式程序 |
最终类图 uml_ultimate.mdj | 在编码后反映真实实现结构 | 重点是追踪一致,保证模型能解释代码 |
预设计类图的价值在于“限制随意编码”。例如在 HW13 中,我的预设计模型已经包含 Book、BookCopy、Bookshelf、AppointmentOffice、BorrowAndReturnOffice、ReservationRecord、UserAccount、BorrowLimitChecker、LibraryManager 等核心对象。虽然当时的架构仍然偏集中式,但至少已经把图书副本、用户账户、预约记录和不同馆藏位置分离出来,没有把所有状态都写在一个大数组里。
最终类图的价值在于“反向校验实现”。例如 HW14、HW15 的最终模型中,RequestHandler、LibrarySystem、LibraryIoAdapter、ArrangeService、MoveService、ReadingRoom、TreasuredBookshelf、ScoreBoard、BorrowRecord 等类都应在最终图中体现。否则就会出现模型和代码脱节:代码已经分层了,但 UML 还停留在旧结构;或者 UML 中存在某个类,但代码中并没有对应实现。
因此,两阶类图不是重复画两张图,而是对应两个不同问题:
第四单元三次作业迭代明显体现了两阶类图的作用。
| 作业 | 代码规模与类结构 | 架构特点 |
|---|---|---|
| HW13 | 约 11 个 Java 类,约 616 行核心代码 | LibraryManager 承担主要调度,已有基础实体类和场所类 |
| HW14 | 约 20 个 Java 类,约 1185 行核心代码 | 引入系统入口、请求处理、移动服务、整理服务、阅览室、热门书架等分层 |
| HW15 | 约 22 个 Java 类,约 1400 行核心代码 | 增加借阅记录、续借、逾期、信用分、信用查询等状态管理 |
从这个过程可以看出,如果没有预设计模型,HW14 和 HW15 的新增功能很容易直接塞进 LibraryManager,使其变成难以维护的“上帝类”。而有了两阶类图后,我能更清楚地判断:新增功能到底应该成为原有类的新方法,还是应该抽象成新类。
例如:
MoveService,避免每个业务方法都手动维护出入库逻辑;ArrangeService,避免整理逻辑和请求处理逻辑混杂;RequestHandler 负责,使 LibrarySystem 只负责读取命令和启动流程;BorrowRecord 负责,而不是让 User 或 BookCopy 单独承担全部语义;User 保存,由 BorrowLimit 和业务操作共同维护,使信用规则有独立位置。这些变化都说明,两阶类图帮助我在迭代中维持架构边界,而不是简单地把新增需求补在原来的代码末尾。
HW13 是第四单元的第一步,核心功能包括借书、还书、预约、取书、查询移动轨迹、开闭馆整理等。我的初始架构主要由以下类构成:
| 类 | 职责 |
|---|---|
LibraryManager | 统一管理图书馆状态和业务流程 |
Book | 表示某一 ISBN 的图书 |
BookCopy | 表示具体副本,维护位置和移动轨迹 |
Bookshelf | 管理在书架上的图书副本 |
AppointmentOffice | 管理预约到达的图书 |
BorrowAndReturnOffice | 管理归还后的图书 |
UserAccount | 管理用户借阅和预约状态 |
ReservationRecord | 管理预约生命周期 |
BorrowLimitChecker | 检查借阅和预约限制 |
这一版的优点是对象划分基本成立,图书副本、用户、场所和预约记录都有独立表示。缺点是 LibraryManager 承担了过多职责:请求分发、状态修改、预约过期、图书移动、开闭馆整理都在同一类中完成。这样在第一次作业规模下尚能维护,但一旦功能扩展,就会出现方法过长、职责交叉、修改影响范围过大的问题。
HW14 在 HW13 基础上引入了更多功能,例如阅览、归还到阅览室、热门书架、评分等。为了应对复杂度,我的架构从单中心逐步拆分为多层结构:
| 新增/强化类 | 作用 |
|---|---|
LibrarySystem | 系统运行入口,负责读取命令并分派 |
LibraryIoAdapter | 适配课程包输入输出,隔离外部接口 |
RequestHandler | 按请求类型处理业务流程 |
ArrangeService | 处理开馆/闭馆整理 |
MoveService | 统一处理图书在不同场所之间的移动 |
ReadingRoom | 维护阅览室状态 |
TreasuredBookshelf | 维护热门/珍本书架状态 |
ScoreBoard | 维护评分与平均分 |
BookCategory | 封装 A/B/C 类图书的可借阅、可预约规则 |
User | 替代 UserAccount,承担更完整的用户状态管理 |
这一版的关键进步是把“请求处理”和“馆藏状态管理”分离。LibrarySystem 不直接处理业务,只负责命令循环;RequestHandler 负责不同请求的业务流程;LibraryManager 更像一个领域对象聚合根,提供图书、用户、场所和服务的访问;MoveService 统一处理移动,减少重复代码。
这个阶段我对架构设计的理解发生了变化:一个类不应该因为“它知道所有对象”就负责所有操作。LibraryManager 可以持有系统状态,但具体业务流程应该由更专门的对象完成。
HW15 继续扩展功能,引入了信用分查询、续借、逾期扣分、借阅期限等规则。最终代码中新增或强化了以下内容:
| 类/方法 | 作用 |
|---|---|
BorrowRecord | 记录借阅期限、是否逾期、是否已经扣分、续借逻辑 |
User.refreshOverdueCredit | 在开馆或操作前刷新逾期扣分 |
BorrowLimit.addCreditScore | 对正常还书、正常归还阅览书等行为增加信用分 |
RequestHandler.queryCreditScore | 处理信用分查询 |
RequestHandler.renewBook | 处理续借请求 |
BookCopy.borrowDeadline | 在副本层面维护借阅到期日 |
BorrowAndReturnOffice.recordOverdueBook | 记录逾期归还图书 |
这一版的难点在于“时间状态”和“信用状态”会影响多个业务流程。借书、还书、续借、开馆刷新、信用查询都可能触发或依赖信用分变化。如果把这些逻辑散落在每个方法里,很容易重复扣分或漏扣分。因此,我将借阅期限封装到 BorrowRecord,并通过 markOverduePenaltyApplied 防止重复扣分。
HW15 让我进一步认识到:当一个状态具有生命周期时,它就不应该只是一个字段,而应当被建模为对象。BorrowRecord 的出现正是因为“借阅”不再只是用户和书之间的一条关系,而是具有起始时间、到期时间、续借次数/效果、逾期状态和扣分状态的业务实体。
从最终实现看,UML 与代码之间可以建立以下追踪关系:
| UML 模型元素 | 代码实现 | 追踪说明 |
|---|---|---|
| 系统入口 | Main, LibrarySystem | UML 中的系统启动对象对应代码的命令循环 |
| 请求处理对象 | RequestHandler | 类图中的业务控制类对应代码中的请求分发和业务流程 |
| 图书馆聚合对象 | LibraryManager | 维护图书、用户、场所和服务对象的整体关系 |
| 图书实体 | Book, BookCopy, BookCategory | 分别对应 ISBN 聚合、副本状态和图书类型规则 |
| 用户实体 | User | 维护借阅、预约、阅览、信用分等用户状态 |
| 预约实体 | Reservation | 维护预约状态、预约副本、过期时间 |
| 借阅实体 | BorrowRecord | 维护借阅期限、逾期、续借和扣分状态 |
| 场所对象 | Bookshelf, TreasuredBookshelf, AppointmentOffice, BorrowAndReturnOffice, ReadingRoom | 对应图书可能所在的不同位置 |
| 服务对象 | ArrangeService, MoveService | 对应整理和移动这两类跨实体操作 |
| 辅助对象 | ScoreBoard, MovingTrace, BorrowLimit, LibraryIoAdapter | 对应评分、移动轨迹、规则检查和 IO 适配 |
这种追踪关系说明,最终代码基本保留了 UML 中的主要抽象。尤其是 HW14 之后,类图不再只是实体类图,而是包含了控制类、服务类和边界适配类,能够较完整地解释程序结构。
方法级追踪关系更能体现模型和实现的一致性。例如:
| 业务需求 | UML/模型方法 | 代码实现 |
|---|---|---|
| 借书 | borrowBook | RequestHandler.borrowBook 调用 findAvailableShelfCopy、checkBorrowLimit、User.borrowBook、BookCopy.moveToUser |
| 还书 | returnBook | RequestHandler.returnBook 查询 BorrowRecord,更新信用分,将书移入 BorrowAndReturnOffice |
| 预约 | orderBook | RequestHandler.orderBook 创建 Reservation 并加入 AppointmentOffice |
| 取书 | pickBook | AppointmentOffice.pickBook 取出副本,Reservation.completeOrder 完成预约 |
| 查询轨迹 | queryMovingTrace | 通过 LibraryManager.getMovingTrace 和 MovingTrace.queryMovingTrace 输出轨迹 |
| 阅览 | readBook | 将副本从书架移动到 ReadingRoom,并记录用户当前阅览书 |
| 归还阅览书 | restoreBook | 从 User 和 ReadingRoom 移除阅览状态,放入借还处 |
| 评分 | gradeBook | ScoreBoard.gradeBook 记录评分,整理时影响目标书架 |
| 续借 | renewBook | User.renewBook 和 BorrowRecord.renewBook 更新到期时间 |
| 查信用分 | queryCreditScore | User.refreshOverdueCredit 后由 LibraryIoAdapter.printCreditInfo 输出 |
通过这种追踪表可以看到,一个业务需求通常不会只落在一个类上,而是由控制对象、领域对象、服务对象共同完成。UML 的意义就在于提前说明这些协作关系,避免代码中出现临时耦合。
最终模型和代码并不是天然完全一致的。我在本单元中也遇到过模型—代码追踪中的问题,例如:
LibraryIOAdapter 与 LibraryIoAdapter 的命名差异,会导致模型和代码追踪困难。represent,评测可能认为不存在合规消息路径。MoveService 或 BorrowRecord,如果类图未更新,就会破坏追踪关系。这些问题让我认识到,UML 与代码的追踪不是一次性工作,而是随迭代持续维护的工作。模型不能停留在“提交材料”,而应当成为解释代码结构的工具。
本单元中比较典型的问题是 UML 模型与程序结构不完全对应。例如顺序图中生命线没有正确关联到类,或者消息路径没有形成评测机认可的合规调用链。其根本原因是我一开始把顺序图理解为“画出大致流程”,而没有充分意识到课程评测会检查模型元素与代码类之间的严格对应关系。
这个问题的教训是:
在图书馆系统中,很多 bug 并不是语法问题,而是边界状态问题。例如:
这些问题的根本原因是状态转移复杂,而我早期没有把“状态机”抽象得足够清楚。后来通过 BookCopy.moveTo...、MoveService、BorrowRecord 等对象将状态变化集中管理,才逐渐降低了这类错误的出现概率。
HW13 中 LibraryManager 过于集中,导致多个业务流程共享同一批内部方法。一旦修改某个移动规则,可能影响借书、还书、预约整理等多个场景。这种 bug 的根本原因不是某一行代码写错,而是职责划分不充分。
HW14 之后,我通过 RequestHandler、ArrangeService、MoveService 分离职责,使请求处理、开闭馆整理、图书移动分别由不同对象负责。这样虽然类数量增加了,但每个类的修改原因更明确,架构风险反而下降。
在第四单元中,大模型对我最有帮助的地方不是直接生成完整代码,而是辅助我进行结构化思考。它比较擅长:
但是,大模型也有明显风险:
represent、消息路径、类名严格对应等细节,大模型未必天然知道。我总结出比较有效的提示方式是:不要一开始就让大模型写代码,而要分阶段约束它。
第一步,要求它只做需求抽象:
请只根据需求列出候选类、每个类的职责、需要维护的状态,不要写代码。
第二步,要求它给出架构边界:
请区分边界类、控制类、实体类、服务类,并说明每个类的修改原因。
第三步,要求它输出 UML 追踪表:
请建立“需求 -> UML 类/方法 -> Java 类/方法 -> 测试点”的追踪表。
第四步,要求它做反例检查:
请列出该架构在迭代中可能出错的边界状态和性能瓶颈。
第五步,才让它辅助局部实现或重构:
请在不改变已有类职责和公共接口的前提下,重构某一个方法。
这种使用方式的核心是:我必须掌握架构控制权,大模型只作为辅助分析和局部生成工具,而不能让它直接决定最终结构。
在 Unit3 的 JML 场景中,大模型也有一定价值,但需要谨慎使用。
大模型可以帮助我:
\old、assignable、异常优先级等问题;大模型最容易忽视的是效率和容器设计。因为它在根据 JML 写代码时,常常倾向于“直接把量词翻译成循环”。这种实现语义上可能正确,但复杂度不一定能接受。例如涉及图结构连通性、最短路径、统计查询、标签成员维护等问题时,如果每次查询都全量遍历,强测很容易超时。
因此,在使用大模型处理 JML 时,我认为必须额外追问:
这个实现每个方法的时间复杂度是多少?
哪些查询可以增量维护?
哪些容器应该从 List 改成 Map 或 Set?
是否存在重复计算?
大模型可以辅助生成基础单元测试,但不能完全替代人工设计测试。比较可靠的方式是让它按规格分组生成测试:
同时,要让大模型解释每个测试对应 JML 的哪一条规格。否则测试很可能只是“看起来覆盖很多”,但没有真正覆盖关键语义。
回顾四个单元,我的架构设计思维大致经历了四个阶段。
第一单元让我开始理解对象抽象的意义。表达式处理不能只靠字符串替换,而需要建立表达式、项、因子等结构。这个阶段我主要学习的是“把问题拆成对象”。
但当时我的架构设计仍然偏功能导向:先想输入怎么解析,再想输出怎么化简。虽然已经有对象,但对象之间的职责边界还不够清楚。
第二单元的多线程电梯让我意识到,对象不仅有属性和方法,还有运行时协作关系。调度器、电梯、请求队列、输入线程之间存在并发交互,如果同步策略设计不好,就会出现死锁、轮询浪费、请求丢失等问题。
这个阶段我的架构思维从“静态对象划分”扩展到“动态协作与线程安全”。我开始重视共享资源的所有权、锁的粒度、线程退出条件和调度策略。
第三单元的 JML 让我意识到,程序不是样例正确就算正确,而是必须满足完整规格。架构设计也不能只看功能模块,还要看每个方法的契约边界。
这个阶段我开始重视:
第四单元进一步要求我把架构显式建模出来。类图、顺序图和代码之间需要能互相解释。这个阶段我开始关注:
因此,我认为自己的架构思维从“能写出来”逐步演进为“能解释、能验证、能迭代”。
我的测试思维也经历了明显变化。
| 单元 | 早期测试方式 | 后来的认识 |
|---|---|---|
| 第一单元 | 构造表达式样例,看输出是否等价 | 需要覆盖递归结构、化简边界和格式边界 |
| 第二单元 | 跑随机请求,看是否能结束 | 需要关注并发时序、线程退出、死锁和性能 |
| 第三单元 | 对照 JML 构造单元测试 | 测试应直接来源于规格,尤其是异常和边界 |
| 第四单元 | 根据业务流程构造场景 | 测试应覆盖状态转移、模型追踪和迭代回归 |
最初我把测试当成“找错工具”,后来逐渐把测试看成“设计的一部分”。好的测试可以反过来检验架构:如果一个功能很难测试,往往说明这个功能的职责边界不清晰;如果一个 bug 很难复现,往往说明状态变化没有被集中管理。
到第四单元时,我更倾向于从状态机角度设计测试。例如一本书从书架到用户、到借还处、再到书架或预约处,这条路径上的每个状态都应该被测试覆盖。用户从无预约到有预约、预约可取、完成取书、预约清除,也应该作为完整生命周期测试。
这门 OO 课程对我最大的影响,不是让我学会某个具体 Java API,而是让我逐渐建立了面向对象开发的完整意识。
第一,我认识到对象不是简单的数据容器。一个好的对象应该同时封装状态和行为,并且有明确的职责边界。像第四单元中的 BorrowRecord,如果只把借阅期限作为字段散落在 User 或 BookCopy 中,系统会很难维护;把它抽象为对象后,借阅生命周期就清晰得多。
第二,我认识到架构设计必须服务于迭代。作业每次都会增加功能,如果初始设计没有扩展点,后续就只能不断打补丁。LibraryManager 从单中心调度逐步拆分出 RequestHandler、ArrangeService、MoveService,正是我对可维护性的逐步理解。
第三,我认识到正确性不仅来自测试,也来自规格和模型。JML 让我从行为契约角度理解程序,UML 让我从结构追踪角度理解程序。测试可以发现错误,但规格和模型可以帮助我提前减少错误。
第四,我认识到复杂系统需要可追踪性。需求、规格、模型、代码、测试之间不能互相脱节。只要其中一个环节发生变化,其他环节也应同步更新。否则程序虽然暂时能运行,但长期看会越来越难维护。
第五,我认识到大模型可以作为有效工具,但不能替代开发者的判断。它可以帮助我梳理类、生成测试、检查边界,但最终的架构边界、课程接口约束、性能判断和模型追踪仍然需要我自己负责。
总体而言,OO 课程让我从“写出能通过样例的程序”逐步走向“设计一个可解释、可验证、可迭代的系统”。这也是我认为本课程最重要的训练价值。
第三单元和第四单元共同构成了我对软件工程化开发的进一步认识:JML 解决的是“行为是否有明确契约”的问题,UML 正向建模解决的是“结构是否有清晰设计”的问题,JUnit 和回归测试解决的是“迭代是否破坏已有行为”的问题。
如果用一句话总结本课程的收获,我认为是:面向对象开发的核心不是把代码写成很多类,而是让每个类都有清晰职责,让每个方法都有明确契约,让每次迭代都有可追踪的模型和可重复的测试。只有这样,程序才能从一次性作业逐渐接近真正可维护的软件系统。