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图,展示了一个典型的嵌入式系统——智能门锁——的上下文关系。我特意把外部实体分成了三类:用户、设备、云服务。

智能门锁系统上下文图 智能门锁 指纹识别 · 密码验证 蓝牙开锁 · 日志记录 住户 指纹/密码/手机 管理员 添加/删除用户 云平台 远程监控/告警 门锁电机 开锁/关锁指令 指纹/密码 配置指令 日志/状态 PWM/GPIO 用户角色 外部系统 硬件设备

4.4 上下文图与用例图的配合

这两张图不是孤立的。我通常的做法是:

  1. 先画上下文图,确定系统边界和外部实体。
  2. 再为每个外部实体画用例图,梳理该角色与系统的交互目标。
  3. 最后检查一致性:上下文图中的每个数据流,在用例图里都应该有对应的用例。
检查清单:
  • 上下文图中的每个外部实体,在用例图里是否都有对应的参与者?
  • 上下文图中的每条数据流,在用例图里是否都能找到对应的用例?
  • 用例图中的每个用例,是否都对应一个完整的用户目标?

4.5 常见错误与避坑

做了这么多年架构评审,我总结了几条高频错误:

错误 表现 后果 纠正方法
边界模糊 把外部系统的功能也画进目标系统 需求范围失控,开发量翻倍 严格区分「系统内」和「系统外」
用例过细 把函数调用画成用例 用例图变成流程图,失去抽象价值 只保留「用户可感知的目标」
遗漏参与者 只画了人,没画外部系统 接口设计遗漏,联调时才发现 列出所有与系统有数据交换的实体
关系滥用 大量使用include/extend,图变得复杂 评审时没人看得懂 只画关键关系,细节留给文档描述
我曾经踩过的坑:有一次画智能家居网关的上下文图,我把「手机App」和「云端服务器」都画成了外部实体,但忘了画「Zigbee协调器」。结果硬件团队按自己的理解设计了串口协议,软件团队按另一套理解写了驱动。联调时发现两边对不上,整整返工了两周。从那以后,我每次画完上下文图,都会拉着硬件、软件、测试三方一起过一遍,确认「所有外部实体都列全了」。

4.6 小结

系统上下文图和用例图,是嵌入式架构文档的「门面」。它们不复杂,但极其重要。一张清晰的上下文图,能让项目组在第一天就达成共识;一套完整的用例图,能让需求分析不再扯皮。

记住:画图不是为了好看,是为了沟通。下次项目启动时,不妨先花半天时间,把这两张图画清楚。你会发现,后面的设计工作顺畅得多。