第四单元及OO课程总结

娜仁丽丽-70066008 2026-06-25 23:58:47

一、第四单元总结

1. 正向建模与开发实践

1.1 正向建模:先设计,后实现

本单元遵循的工作流程:

明确需求 → 设计(UML)→ 实现 → 对齐修正

第一阶段,我们根据需求用UML画出图书馆系统的设计图。这迫使我们在写任何代码之前,就要想清楚系统中有哪些实体、各自负责什么、边界在哪里。

确认好设计图之后,才进入实现阶段。第二阶段,我们测试并修复代码,再回头更新设计图,使其与实际实现保持一致。

一阶图和二阶图之间的差距并不是失败。它是最真实的记录,记录了我们最初的假设在哪些地方出了问题。

1.2 与第三单元规格先行方法有何不同?

两者都要求在实现之前做出决策和规划,但关注的层面不同。

正向建模规格先行
关注点结构——系统中存在什么、各部分如何关联行为——每个方法必须做什么
产出物UML类图/状态图JML契约
分析层面系统层面方法层面
对应单元第四单元第三单元

可以把它们理解为同一种"设计先行"理念的两个层次:正向建模搭出骨架,规格先行为每个方法定义契约。两者合在一起,才能在写代码之前覆盖完整的设计过程。

1.3 为什么这很重要?

这让我们小小体验了一把真实商业工程的工作方式。

大型科技企业中有一个特殊岗位叫做前沿部署工程师(FDE)——他们处于工程团队和客户之间,负责在真实客户环境中部署和定制产品。这要求他们既要深入了解产品的实现方式,也要能坦诚地告诉客户什么可以做、什么不行,并在正式开发前向客户展示可行的产品模型。

初始模型与最终交付物之间的差距越小 → 返工越少、对其他开发者的指导越准确、客户越满意。

我们的两阶类图正是这个道理。一阶图是"提案"——在实现之前就锁定了设计方向。二阶图是诚实的修正——记录了我们实际建出来的东西。


2. 架构设计总结

整个系统通过三次作业迭代完成,每次在已有基础上叠加新需求:

版本新增功能架构影响
HW13基本借还、预约功能建立基础架构
HW14阅览室、精品书架、评分引入 ReadingRoomTreasuredBookshelfIBookshelf 接口
HW15信用分、借阅期限、续订User 新增 creditScoreBookCopy 新增 dueDate,新增 renewed 处理器

最终架构以 LibraryManager 为主控制器,持有所有地点对象并分发所有请求:

img

关键追踪关系——代码与一阶图的主要偏差:

  • Library 最初设计为直接处理请求,实现后发现它变成了一个上帝类,于是引入 LibraryManager 作为控制器,Library 退化为纯数据目录。
  • IBookshelf 接口在一阶图中并不存在。实现 TreasuredBookshelf 时发现两个书架的操作完全相同,于是提取出这个接口。
  • arrange() 方法最初是一整块。实现过程中逻辑过于密集,不得不拆分为 sweepReadingRoom()expireOrders()moveBroToShelves()redistributeShelves() 四个方法。

📌 [此处插入UML类图、状态图和顺序图]


3. 使用大模型辅助正向建模

3.1 一次实验:让大模型做挑战者,而不是答题机

在前几个单元中一直依赖大模型直接给出解决方案,后来开始觉得这样不太对劲。于是在HW13中,我尝试了一种不同的方式——自己完成一阶图设计,只用大模型来压力测试它。

📌 [此处插入草稿设计图]

我把设计上传给Claude,让它来挑战我的方案。而当我提问时,它不直接回答,而是把问题抛回来:

我问:
关于请求管理,哪种方式更安全、性能更好?
- 在每个地点存储待移动书籍的请求
- 根据位置状态(AO/BRO/User/Bookshelf)搜索图书

Claude回:
你问的是:在每个地点存储移动请求,还是按位置状态搜索?
从两个角度思考:
- 整理操作发生得频繁吗?
- 图书总共有多少本?最多100个ISBN × 10个副本 = 1000本

在这个规模下,性能真的重要吗?
哪种方式在编写整理逻辑时更清晰?

经过多轮问答,修改了5稿左右,一阶图终于定稿。然后开始写代码——然后发现还是有很多地方需要改。😔

技术可行性分数不太好看,但这次实验让我学到了更有价值的东西:被迫为每一个设计决策进行辩护,让我真正理解了系统本身——实体是什么、各自负责什么、边界在哪。我也发现了自己的一个习惯:总是倾向于把设计压缩到最简,图上看起来整洁,但实现时方法逻辑就会过载。arrange() 就是最典型的例子——图上是一个整齐的方法,代码里最终拆成了四个。

3.2 大模型在正向建模中的有效用法

核心结论:用大模型挑战你已有的设计,而不是让它替你生成一个。

让Claude从需求出发直接生成架构,结果看起来合理——但我没办法解释其中任何一个决策,因为根本不是我思考出来的。这就是陷阱所在。

真正有效的方式是反过来。三个实用模式:

① 一起走一遍场景
描述一个具体流程——比如从预约到取书的完整流程——让Claude逐步走过你的类设计,指出哪里职责不清或有遗漏。

② 问"各自让什么变难了",而不是"哪个更好"
"哪个设计更好?"只会给你一个答案。"每个设计让什么变得更难?"才能给你权衡分析。最终的决定还是你来做。

③ 问哪里实现起来会很痛
"这个设计哪部分最难写?"这个问题出奇地准。在HW13中,它准确预判了单一 arrange() 方法会失控——后来确实发生了。

问题不在于大模型的答案是否正确,而在于直接拿到答案,跳过了正向建模本来要你完成的思考过程。


二、课程总结

4. 架构设计思维的演进

每个单元都在问同一个问题,只是答案一直在变:类到底是什么?

第一单元 → "谁用谁?"            (类是数据容器)
第二单元 → "谁用谁?什么时候用?"  (类是需要协调的参与者)
第三单元 → "谁用谁?怎么用?"     (类具有行为契约)
第四单元 → "这个东西是什么?"     (类有明确的身份和职责)

第一单元里,类基本上就是一个有名字的数据盒子。设计就是决定哪个盒子访问哪个盒子。

第二单元加入了时间维度。有了线程,光知道谁访问谁不够——还要考虑什么时候访问,以及是否线程安全。类开始感觉像是需要相互同步的参与者,而不只是容器。

第三单元转向了方式。JML要求你精确说明一个方法做什么——进入时的前置条件,返回时的后置条件。一开始觉得很烦,但它让测试清晰了很多。有规格,就有测试。

第四单元在最前面加了一个新问题:这个东西是什么? 在写任何代码之前,就要在图上承诺每个类的身份和职责。回头看,四个单元衔接得相当自然——静态结构 → 时序协调 → 方法契约 → 系统级身份与职责。


5. 测试思维的演进

第一单元:运行 → 看输出 → 往回追
第二单元:打日志 → 看事件顺序 → 往回追
第三单元:写规格 → 写测试 → 在运行前预判bug
第四单元:过滤 → 隔离单个实体的时间线 → 找偏差点

第一单元很直接:运行,看输出,找bug。在一切都是确定性的情况下,这完全够用。

第二单元把这套方法打破了。有了线程,同样的输入在不同的运行中会产生不同的结果。唯一稳定有效的方法是用 System.err.println 实时追踪发生了什么。我不再问"出了什么错",开始问"什么时候出错、以什么顺序出错"。在多线程代码里,不打日志就是在瞎猜。

第三单元引入了提前捕获bug的思路。从JML规格出发写测试,意味着在运行任何东西之前就要把边界情况想清楚。有一点值得注意:规格是下限,不是上限。你的实现可能在规格完全没提到的地方出bug,所以测试要比规格覆盖得更广。

第四单元是最棘手的。因为交互式评测机根据你的输出动态生成请求,修一个bug就会改变整条执行路径——原来的bug就没办法复现了。有效的方法是过滤:只针对一本书或一个用户打印 System.err 输出,把他们的完整历史当作时间线来读,找到时间线在哪里偏离了规格要求。


6. 课程总体收获

第一单元——一个基本习惯开始形成:问"这件事应该由谁负责?"设计不好看,但问题问对了。

第二单元——正确性不只是得到正确的值,还关乎事情以正确的顺序在正确的时间发生。并发bug让人清醒:它们只在两件事在恰好错误的时刻碰撞时才会出现,光静态阅读代码是看不出来的。

第三单元——意料之外的自信。写JML、从规格推导测试,让我意识到自己可以独立地严格推理方法的行为,不需要工具来替我判断。不管我多么希望自己的设计完美,规格才是最终的裁判,实现必须满足它。这是一种更踏实的思考方式。

第四单元——设计成了核心。一阶图和二阶图之间的差距,直接反映了我在开始写代码之前对问题理解得有多深。这个差距永远不会归零,目标只是每次让它小一点。


四个单元走下来,轨迹是这样的:

静态结构 → 动态协调 → 方法设计 → 系统设计

回头看是一条很清晰的路——尽管在截止日期前的凌晨两点,完全感觉不到。

说真的,这门课是我见过的对拖延症最有效的治疗方案。

...全文
331 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

309

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧