308
社区成员
发帖
与我相关
我的任务
分享Unit4 的三次作业(hw13→hw14→hw15)是课程中唯一采用"先建模,后编码"正向开发流程的单元。核心工具是 StarUML 中绘制的两阶类图——类图和状态图。
在每个作业编码前,我先在 StarUML 中绘制完整的类图,确定三个要素:
BookCopy 持有 Isbn、BookState、ArrayList<MovingTrace> 三个核心属性;User 在 hw15 中扩展 credit(信用分,初始100,上限180)、dueDates(到期日映射)、penalizedOverdue(已罚逾期去重集)等信贷字段。Library/LibraryManager 作为门面组合 Bookshelf、BorrowReturnOffice、AppointmentOffice、ReadingRoom 等地点组件,各组件间互不感知,符合"高内聚、低耦合"原则。类图的正向作用:强制架构思考前置——在编码前就必须回答"每个类知道什么、能做什么";依赖方向在类图中一目了然;从 hw13 到 hw15 每次迭代先改图再改码,类图成为变更的可视化记录。
BookCopy 的状态转移是系统最核心的动态行为。hw13 设定了 4 个状态(BOOKSHELF、BORROW_RETURN_OFFICE、APPOINTMENT_OFFICE、USER)约 10 条迁移。hw14/hw15 扩展为 6 个状态(新增 TREASURED_BOOKSHELF、READING_ROOM),迁移数增至 30+。
@Trigger 注解将状态图与代码精确绑定。每个可能触发状态转移的方法都用 @Trigger(from = "...", to = "...") 标注,使 UML 模型可以自动验证代码是否正确实现了设计的状态机。状态图确保了三件事:每个状态下所有 Trigger 都有定义、无死状态(每个状态有入必有出)、无矛盾 Guard。
类图和状态图不是孤立的。类图中的方法(如 borrowBook、returnBook)对应状态图的 Trigger;状态图的 Guard(如 credit > 80)对应类图中属性的值域约束。实际工作流程为:需求→修改类图→更新状态图→交叉验证(方法是否覆盖所有 Trigger?Guard 在代码中是否有对应判断?)→编码实现。这种双图驱动让 hw15 即使新增信用系统、逾期罚款、续借、奖惩等大量功能,核心架构依然稳定,没有"改一处崩全局"。
Library 作为门面(Facade 模式),组合 Bookshelf、BorrowReturnOffice、AppointmentOffice 三个地点组件,以及 HashMap<String, User> 管理用户。BookCopy 在 4 个状态间流转。借阅规则(B类限1本、C类限每种1本)由 BorrowLimit 静态工具类集中管理。整理流程在 Open/Close 时触发:BRO→BS、BS→AO(预约到书)、AO超期→BS。Library 升级为 LibraryManager,新增 ReadingRoom 和珍本书架(treasuredShelf,Bookshelf 的第二个实例)。引入评分系统后,书籍在普通书架和珍本书架之间迁移取决于 isTreasured(isbn) 的代码判断——这是一个关键设计决策:书架选择逻辑放在代码中而非 UML Guard 条件中,因为评分是运行时动态变化的。新增 READ 和 RESTORE 操作,以及 GRADE 评分命令。User 新增信贷子系统:credit(100起,180上限,0下限)、dueDates(B类15天、C类30天到期)、penalizedOverdue(防重复罚款的去重集合)。信用奖惩机制:按时归还 +10、按时归还阅读书籍 +10、逾期每天 -15、预约未取 -15、未归还阅读书籍 -10。新增 RENEW 续借(仅未逾期时可续,延长7天)和 query_credit 查询命令。Guard 条件映射到信用门槛:credit > 80 可借阅/预约/取书、credit > 40 可读A类书、credit > 0 可读B/C类书。| UML 元素 | 代码对应 | 追踪机制 |
|---|---|---|
| UMLClass | Java 类 | 类名一一对应 |
| UMLAttribute | Java 字段 | 属性名与类型对应 |
| UMLOperation | Java 方法 | @Trigger 注解绑定 |
| State | BookState 枚举 | 状态名对应枚举常量 |
| Transition | copy.move() 调用 | @Trigger(from, to) 注解 |
| Guard | if 条件判断 | Guard 文本→代码条件表达式 |
@Trigger 注解是最强的追踪纽带。例如 @Trigger(from = "BOOKSHELF", to = "USER") 修饰 borrowBook 方法,从 UML 状态图出发可定位到精确的代码实现方法,从代码出发可反向验证 UML 中是否有对应迁移——形成双向可追溯链。
在 hw15 开发中,状态图中 14 条 Guard 条件存在矛盾——进入珍本书架需 avgScore >= 4,离开需 avgScore < 4,两个互斥条件导致某些路径在形式语义上不可达(评测反馈:"从起始状态到 Bookshelf 的某条简单路径无解")。修复方案:将这些 Guard 从 UML 状态图中删除,改为在 moveToShelf() 方法中通过 isTreasured(isbn) 动态判断。最终保留了 4 条有意义的 Guard(credit > 80、credit > 40、credit > 0),因为它们表达的是稳定的业务权限规则。
启示:UML 模型适合描述静态业务规则约束,不适合表达运行时数据动态性。正向建模不是"画完图就完事"——编码中发现的模型缺陷需反哺到 UML 图中,形成双向修正闭环。
继续使用 DeepSeek 辅助开发。与前三个单元专注于"怎么写代码"不同,本单元的核心挑战是"怎么设计模型"——这正是大模型相对薄弱的领域。
.mdj 文件本质是 JSON。想好类的设计后让模型生成对应的 JSON 节点,避免手工编辑的繁琐和格式错误。LibraryManager 早期版本同时持有 Bookshelf 引用和 BookCopy 直接列表——这是"穿透式"依赖,应统一通过 Bookshelf 访问 BookCopy。Expr→Term→Factor)。将数学语法直接翻译为类结构,设计服从算法。局限:前瞻性不足,hw2→hw3 扩展多变量和导数时 Poly 内部表示经历了较大重构。InputThread→RequestTable→Dispatcher→Car)和锁粒度设计。认识到架构不仅是空间结构(类关系),也是时间结构(锁依赖图、线程等待链)——死锁风险来自后者。NetworkInterface 等)的精确实现,requires/ensures/assignable/signals 构成不可违反的约束边界。设计的好坏首先取决于"是否正确",而非"是否巧妙"。Unit1: 算法→类层次 Unit2: 类层次→线程协作
Unit3: 自由设计→契约约束 Unit4: 契约约束→模型驱动
从"代码能跑就行"到"编码前在模型层面推理验证"——这大概就是"设计与构造"的真正含义。
TimableOutput 还原事件序列定位死锁点、TimableInputStream 时间加速在秒级重现分钟级场景、用大模型构造极端边界数据(全员同层、大量维护并发、F2 碰撞边缘等)。@Parameterized 让 13 种图结构共用约 20 行测试逻辑。从 JML 规格推导测试:requires→等价类划分、ensures→验证断言、signals→异常优先级测试序、assignable \nothing→状态快照对比。@Trigger 验证一致性,最后用全生命周期场景(购入→上架→借出→归还→预约→取书→评分→入/出TBS→逾期→续借)验证端到端状态流转。Unit1: "测我能想到的" Unit2: "测那些概率性出现的"
Unit3: "测规格所要求的" Unit4: "测设计本身的正确性"
OOP 核心概念(封装、继承、多态、抽象)在四个单元的多范式实践中内化:递归下降解析→多线程并发→契约式设计→模型驱动开发。每个单元的编程范式差异很大,避免了陷入单一思维定式。Git、JUnit、StarUML、IDE 等工具链已成肌肉记忆。大模型使用形成了明确的"人机分工"认知——模型是执行助手,架构决策仍需人的判断。
最大转变:从"如何让代码跑起来"到"编码前如何设计正确、可扩展、可验证的系统"。JML 让我认识到正确性可以通过形式化契约系统追求;UML 让我认识到设计是可以独立于编码进行并提前验证的。hw13→hw15 的顺利迭代证明:好的初始设计让扩展变得自然,好的抽象让变更的涟漪最小。
四个单元的 bug 类型完全不同——逻辑错误(U1)、死锁/活锁(U2)、JML 实现偏差(U3)、Guard 条件矛盾(U4)——每种都需要不同的 debug 策略。这培养了"先定位问题类型,再选择分析工具"的元能力。Unit3 击鼓传花游戏更是让我深刻体会到规格精确性在协作中的重要性——一个微小歧义传递 6 次后变得面目全非。
"面向对象设计与构造"——设计在前,构造在后。这不再是课本上一句话,而是四个单元下来亲身验证的道理。