第 6 章 · UML 与建模

UML(统一建模语言)是软件的「工程制图标准」,十四种图覆盖结构与行为。它的名声也两极:有人画全套图交作业,有人极度反感「图不等于代码」。本章的立场是:图是思考的脚手架,不是交付物。挑最常用的四种图(用例、类图、时序图、状态图)讲透,配现代的替代品(轻量记号、代码即文档),目标是你「该画图时会画,画完知道画图的边界」。

6.1 什么时候画图

先立边界。画图的三种正当理由:

  • 理清自己:设计盘根错节时,画图强迫你把模糊的想法显式化(画的收益在画的过程);
  • 沟通他人:向新成员讲系统、向 reviewer 解释跨模块的流程,一张时序图省半小时白板;
  • 存档关键决策:架构图(第 4 章)随 ADR 一起,给未来的人考古。

不画图的正当理由也明确:图会过期(代码改了图没人维护)、图不参与编译(错误编译器不报)、全套 UML 是体力税。「代码 + 测试 + 少量关键图」是现代默认;需要重图的领域(嵌入式状态机、复杂协议)才全套上。

6.2 用例图:系统的「对外承诺」清单

用例图最简单:人形是角色(actor),椭圆是用例(系统为角色做的事),连线是关系。它回答「系统给谁、提供什么价值」,是需求章(第 3 章)用户故事的图形化。

   学生 ──── (借书)
     │  ──── (还书)
     │  ──── (查询馆藏)
   馆员 ──── (催还)

画它的价值在收敛讨论:「报修系统」的用例图一挂白板,「管理员要不要能代学生提交」这类边界问题当场暴露。它不表达流程细节(那是 6.4 时序图的事),保持粗粒度。

6.3 类图:静态结构的契约

类图是面向对象设计(Java 课第 7、8 章)的主图:类名、字段、方法三层框,类间关系四种:

关系含义UML 记号例
关联 association持有引用实线箭头订单 →→ 客户
聚合 aggregation整体-部分(可分离)空心菱形班级 ◇ 学生
组合 composition整体-部分(同生共死)实心菱形订单 ◆ 订单项
泛化 generalization继承空心三角学生 ▷ Person

组合与聚合的区分(生命周期)在面试高频;对应到代码,组合 = 字段里 new 出来的、随容器生死(Java 的组合默认靠构造里创建),聚合 = 注入进来的外部对象。

画类图的实操要点:只画「讲设计必需」的成员。类图的价值是让 reviewer 看见「职责划分与依赖方向」(第 4 章),不是把 IDE 自动生成的 UML 导出来打印。

6.4 时序图:一次协作的完整剧本

时序图是最「值回票价」的行为图:纵向是对象的生命线,横向箭头是一次次方法调用(自上而下按时间),回执用虚线:

用户      Controller      Service        Repository
 │  POST /orders │               │               │
 │ ─────────────▶│ createOrder() │               │
 │               │──────────────▶│ validate()    │
 │               │               │───save()────▶ │
 │               │               │◀───orderId────│
 │ ◀── 201 {id} ─│◀───orderId────│               │

它强迫你回答所有「中间环节」:谁调用谁、参数是什么、返回什么、异常路径怎么走。画一张「下单」的时序图,架构的接口设计(第 4 章)顺手就完成了大半。画完再检查一次依赖方向:Service 认识 Repository,Repository 不认识 Service,反向箭头 = 设计问题。

6.5 状态图:有「模式」的对象必备

对象的生命有明确状态且状态迁移有规则时,状态图(state machine)是唯一不丢信息的描述:

[待支付] ──支付成功──▶ [已支付] ──发货──▶ [已发货] ──签收──▶ [已完成]
   │──超时──▶ [已取消]      │──退款──▶ [已退款]

订单、工单、审批流、游戏角色(待机/攻击/受击),全是状态图的领地。它的工程价值:迁移规则显式化(什么状态能到什么状态),直接变成测试用例(每条边至少一条测试,第 8 章)和数据库的状态字段校验。漏掉状态图的状态机代码,几乎必然出现「已取消的订单被发货」这类事故。

6.6 现代轻量替代品

全套 UML 工具(Enterprise Architect)重得离谱,现代工程常用轻量替代:

  • 文本生成图:Mermaid(markdown 内嵌,支持时序图/流程图/类图)、PlantUML,图随代码进 git,可 diff;
  • C4 模型:架构文档的现代标准(Context/Container/Component/Code 四层,按受众分层讲架构);
  • 代码即文档:接口签名本身就是最准的「类图」,OpenAPI/Swagger 就是「接口的时序图合同」。

本 wiki 自己就用 Mermaid 的理念(能文本的不画图)。工具越轻,画图的门槛越低,「画在白板上讨论完就擦」其实是最高频的真实用法。

常见错误

  • 全套 UML 交作业。时间花在画图上,代码没写完;按 6.1 的三条理由问自己「这张图给谁看」。
  • 图与代码脱节。三个月后的图还是老的,误导新人;图放 git(Mermaid)随代码更新,或标注「快照日期」。
  • 类图里塞满字段方法。六百行一张图没人看;讲设计就画「关系与职责」级,细节让代码说话。
  • 状态图漏异常路径。只画 happy path;每条边的「不允许」同样要显式(拒绝迁移)。

练习

  1. 动手:给你(或熟悉的)项目画一张 C4 的 Container 图(方框=服务/库,箭头=调用与协议),给一个新同学看,让他讲回「系统怎么工作」,漏讲处即图的问题。
  2. 动手:用 Mermaid 画「借书」的时序图(读者 → 界面 → 借阅服务 → 数据库 → 邮件服务),故意包含一条「库存不足」的异常分支。
  3. 动手:把你的课设的状态流转画成状态图,对照代码检查:是否有代码允许的、图上没有的迁移?(那是设计债。)
  4. 思考:「图会过期」的根源是图与代码两套真相。对比三种解法(接受过期+标注日期、文本图随 git、从代码自动生成 UML),评估各自的实际成本与效果,给你自己的项目选一个并说明理由。