4、系统上下文图与用例图:使用UML绘制系统边界,明确外部交互
做嵌入式架构这么久,我见过太多项目栽在同一个坑上——系统边界没画清楚。
你想想看,硬件团队以为某个功能由软件实现,软件团队觉得那是硬件的事,最后联调时才发现两边都没做。这种锅,谁背?
所以这一章,咱们就聊聊怎么用UML把系统边界画明白。说白了,就是让所有人都知道:我们的系统到底管什么,不管什么。
4.1 系统上下文图:一张图说清系统与外界的关系
系统上下文图(System Context Diagram),是架构文档里最顶层的那张图。它不关心内部细节,只回答三个问题:
- 我们的系统叫什么?
- 谁在和它交互?
- 交互什么?
我个人习惯,在项目启动的第一周就把这张图画出来。哪怕需求还不完整,先画个草图,让各方确认「边界」对不对。
4.1.1 核心元素
| 元素 | UML表示 | 说明 |
|---|---|---|
| 目标系统 | 矩形框(通常带阴影) | 我们正在开发的嵌入式系统 |
| 外部实体 | 矩形框(无阴影) | 人、设备、其他系统 |
| 数据流 | 带箭头的连线 | 数据/信号/命令的流向 |
关键原则:系统上下文图里,目标系统只有一个。如果你画了两个以上的系统框,说明你画的是架构图,不是上下文图。
4.1.2 实战案例:智能温控器
我曾经帮一个团队梳理智能温控器的系统边界。一开始他们画了十几张图,越画越乱。我让他们退回到上下文图,只画一张——结果问题全暴露了。
// 系统上下文图示例(文本描述)
+------------------+ +------------------+
| 手机App | | 云平台 |
| (用户设置温度) |<----->| (远程监控) |
+------------------+ +------------------+
| |
| 蓝牙/WiFi | MQTT
v v
+--------------------------------------------------+
| 智能温控器(目标系统) |
| - 温度采集 |
| - 继电器控制 |
| - 本地显示 |
+--------------------------------------------------+
^
|
| I2C
|
+------------------+
| 温度传感器 |
| (DS18B20) |
+------------------+
你看,这张图一出来,团队立刻发现:云平台和手机App是两个独立的外部实体,它们的通信协议、数据格式、安全要求完全不同。之前混在一起讨论,难怪扯皮。
我的习惯:画上下文图时,用不同颜色区分「人」「设备」「系统」三类外部实体。这样评审时一目了然。
4.2 用例图:从用户视角看功能
上下文图画的是「谁和系统交互」,用例图则回答「交互为了什么」。
用例图(Use Case Diagram)是UML里最容易被误解的图。很多人把它画成了功能列表——这是错的。用例图的核心是用户目标,不是系统功能。
4.2.1 用例图的构成
- 参与者(Actor):可以是人,也可以是外部系统。注意,参与者是角色,不是具体的人名。
- 用例(Use Case):一个完整的目标,用动宾结构命名,比如「设置温度」「查看历史数据」。
- 关系:参与者与用例之间的关联,以及用例之间的包含(include)、扩展(extend)、泛化(generalization)。
避坑指南:我曾经见过一个团队,把「读取传感器」「解析数据包」「更新显示」都画成了用例。这是典型的「把功能当用例」。正确的做法是:用户的目标是「查看当前温度」,至于系统内部怎么读传感器、怎么解析,那是设计的事,不是用例。
4.2.2 用例关系详解
| 关系类型 | UML表示 | 含义 | 示例 |
|---|---|---|---|
| 包含(include) | <<include>> | 一个用例必然包含另一个用例 | 「设置温度」包含「验证用户权限」 |
| 扩展(extend) | <<extend>> | 可选行为,条件触发 | 「设置温度」可扩展「发送告警」(当温度超限时) |
| 泛化(generalization) | 空心三角箭头 | 父子关系,子用例继承父用例 | 「设置温度」和「设置湿度」泛化为「设置参数」 |
4.3 用SVG画一张系统上下文图
下面这张SVG图,展示了一个典型的嵌入式系统——智能门锁——的上下文关系。我特意把外部实体分成了三类:用户、设备、云服务。
4.4 上下文图与用例图的配合
这两张图不是孤立的。我通常的做法是:
- 先画上下文图,确定系统边界和外部实体。
- 再为每个外部实体画用例图,梳理该角色与系统的交互目标。
- 最后检查一致性:上下文图中的每个数据流,在用例图里都应该有对应的用例。
检查清单:
- 上下文图中的每个外部实体,在用例图里是否都有对应的参与者?
- 上下文图中的每条数据流,在用例图里是否都能找到对应的用例?
- 用例图中的每个用例,是否都对应一个完整的用户目标?
4.5 常见错误与避坑
做了这么多年架构评审,我总结了几条高频错误:
| 错误 | 表现 | 后果 | 纠正方法 |
|---|---|---|---|
| 边界模糊 | 把外部系统的功能也画进目标系统 | 需求范围失控,开发量翻倍 | 严格区分「系统内」和「系统外」 |
| 用例过细 | 把函数调用画成用例 | 用例图变成流程图,失去抽象价值 | 只保留「用户可感知的目标」 |
| 遗漏参与者 | 只画了人,没画外部系统 | 接口设计遗漏,联调时才发现 | 列出所有与系统有数据交换的实体 |
| 关系滥用 | 大量使用include/extend,图变得复杂 | 评审时没人看得懂 | 只画关键关系,细节留给文档描述 |
我曾经踩过的坑:有一次画智能家居网关的上下文图,我把「手机App」和「云端服务器」都画成了外部实体,但忘了画「Zigbee协调器」。结果硬件团队按自己的理解设计了串口协议,软件团队按另一套理解写了驱动。联调时发现两边对不上,整整返工了两周。从那以后,我每次画完上下文图,都会拉着硬件、软件、测试三方一起过一遍,确认「所有外部实体都列全了」。
4.6 小结
系统上下文图和用例图,是嵌入式架构文档的「门面」。它们不复杂,但极其重要。一张清晰的上下文图,能让项目组在第一天就达成共识;一套完整的用例图,能让需求分析不再扯皮。
记住:画图不是为了好看,是为了沟通。下次项目启动时,不妨先花半天时间,把这两张图画清楚。你会发现,后面的设计工作顺畅得多。