ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SysML建模实战:五张图构建可验证的需求-设计-验证工程链路

SysML建模实战:五张图构建可验证的需求-设计-验证工程链路

简介:《Systems Engineering with SysML/UML》是一本系统讲解OMG系统建模标准的英文原版PDF,面向系统工程人员、软件架构师与模型驱动开发者,适合借助统一建模语言进行复杂系统设计、需求追踪和架构分析的读者。资源为单个PDF文件,压缩包体积3.47MB,便于下载阅读。书中覆盖系统建模语言与统一建模语言的基础知识、二者差异,结合需求分析、架构设计、系统验证等系统工程方法论,通过实际案例展示完整建模流程;同时详解需求视图、结构视图、行为视图等系统视图,以及需求追踪、架构评估、动态仿真与验证等关键方法,有助于建立从需求到验证的闭环思维。此外,还介绍主流建模工具的使用与选型建议,提炼业界最佳实践,并针对航空航天、汽车、医疗设备等行业给出典型系统建模场景,帮助读者从理论走向工程实施。目前已有232人学习下载,适合作为系统工程建模领域的案头参考书。

1. 用 SysML 做系统工程:为什么画图比写更厚的文档更先值得投入

我见过太多这样的项目:设计文档写了四百多页,每个专业都在补自己的章节,可一到样机联调就发现电源给主控的时序对不上、结构给的接口位置早就是旧版、软件和硬件各拿一张完全对不上的接口表。SysML 要解决的不是「再写详细一点」,而是让需求、结构、行为、参数这四类信息,在同一个模型库里彼此关联、能查、能算、能校验。这篇文章只讲一条主线:怎么用 SysML/UML 这套建模语言,把分散的文档体系收敛成一条真正能验证的工程链路,适合正在做系统设计、技术管理、跨专业接口协调,或者刚从软件方向转到系统方向的从业者。

2. SysML 图形体系:不是给 UML 加前缀,而是把工程约束显式化

2.1 需求图与用例图:系统边界怎样从文字变成图形

SysML 脱胎于 UML,但并不是在 UML 的图上贴一层工程标签就完事。UML 最初面向软件系统的对象建模,SysML 则把视角拉回「系统」本身:它保留了对表达行为有帮助的用例图、活动图、状态机图,新增了需求图和参数图,并把块、端口、流定义成比软件类更贴近物理系统的建模元素。对做系统的人而言,最明显的改变是需求不再只是文档里的一个段落,而是模型库里带唯一编号、能被引用、能挂关系、能统计状态的对象。

我动手建模时第一个图通常画需求图,但有一条纪律:不要把需求图画成思维导图。思维导图的箭头没有语义,而需求图里的关系是有明确含义的。常见的有四类:derive 表示子需求从哪条父需求派生而来,refine 表示某条需求被哪条更具体的设计需求细化,satisfy 表示哪个块真正实现了这条需求,verify 表示这条需求由哪项测试、仿真或评审来验证。真实项目中,前期把 derive 和 satisfy 用明白就够支撑绝大多数追踪工作,把所有关系类型都铺上去反而会让评审会不知道先看什么。

操作上我习惯这样控制节奏:先给需求编号,编号规则用「R-系统级-模块-三位序号」,比如 R-SYS-CLT-001,表示系统级、客户端模块、第一条需求。编号一旦建立就不允许改动,哪怕后来需求文字改了很多版,编号也要保持不变。接着,在需求图上只画需求树和状态属性,状态属性可以选「已批准、评审中、已冻结」。这里有个好用的工程习惯:评审前把「未冻结」的需求过滤一遍,模型实体凡是挂在这些需求上的一并不允许进入下一阶段,这比翻文档找「这条需求到底定了没」快得多。

需求树稳定后,用例图就该登场。用例图回答的是系统边界问题:谁是外部角色,系统对外提供哪些能力。角色不一定是人,可以是另一个系统、硬件设备、时钟,甚至网络服务。我一般会在块定义图之前先画用例图,因为后续要补哪些外部接口,基本在用例图阶段就能看出来。项目里常见的翻车现场是用例图只画了两个角色,比如「终端用户」和「云端服务」,等到联调才发现漏掉了运维角色对配置管理的诉求,整个系统的配置接口根本没建模过。

2.2 块定义图与内部块图:结构颗粒度决定接口会不会错

块定义图(BDD)和内部块图(IBD)是非常容易混淆的一对图。BDD 画的是系统的组成关系和层级,回答的是「系统由哪些块组成、块与块之间的引用与组合关系」;IBD 画的是块内部端口和连接器,回答的是「这些块之间到底怎么接线」。很多刚接触 SysML 的工程师把两块混在一张图里画,结果画出来既不像层级树也不像接线图,审核的时候找不到接口。

我的做法是把 BDD 停在子系统或组件层,不继续往下钻到零件级。举个例子,一个设备的系统级 BDD 画到电源模块、主控模块、通信模块、结构模块这一层就收手;真正要把内部连接画清楚,放在单独的 IBD 里,而且只画当前版本要重点验证的子系统。把整台设备画到螺栓级别,图上一百多个块,连线和交叉能把评审逼疯,而且没人能找到这轮改动到底影响谁,最后这张图就被所有人无视,变成一张挂在墙上好看的示意图。

IBD 里最值钱的信息是端口。SysML 里有标准端口和流端口,标准端口传信号,流端口带方向地把数据、能量、物料从一个块送到另一个块。流端口必须有方向,没有方向的流端口基本等于没建模。我见过很多内部块图,线画得密密麻麻,但两边块上根本没有端口,接口表全靠看图的人脑补,最后得出完全不同的结论。所以我给自己定了一条规矩:IBD 里两个块之间的连接线必须从一个端口出发,不允许从块边缘直接拖线到另一个块边缘。工具里配置上端口类型库,新建连接时强制选择端口,这条纪律就能在操作层面被兜住。

下面这个文本建模脚本是我画草图时常用的格式,它会生成一张简化的块定义图。这种文本脚本适合在会议现场快速调整层级,真正的正式模型仍然要落到建模工具里补全端口和约束属性。

@startuml title 块定义图:某个跨平台数据同步系统 block "移动端 App" as app { block "同步引擎" as engine block "本地缓存" as cache } block "服务端" as srv { block "同步接口" as api block "任务队列" as queue } app *-- engine app *-- cache srv *-- api srv *-- queue engine --> api : 上行数据流 engine --> cache : 读写本地 @enduml

这段脚本里,*--表示组合关系,意思是一个块销毁时它内部所属的块也一起销毁;-->是带方向的关联,这里的两个箭头分别表示数据流和读写调用。参数说明里值得注意的一点是:文本脚本只能表达连接关系,端口和流类型必须进入真正的 SysML 模型后补充,否则这张图依然只是「示意图」,无法支撑接口一致性分析。我一般在脚本稳定后,用建模工具把 api 块和 engine 块之间的流端口定义成「上行数据流」和「下行确认流」,并注明数据类型,这一步才是接口建模的本体。

2.3 活动图与状态机图:行为画到哪一层才不是装饰图

行为建模是 SysML 里最容易画成装饰图的部分。活动图和状态机图各有适用场景,选错类型会让整个模型越画越沉。我的判断标准很简单:需求里出现「当某条件满足并持续处于某模式时」,用状态机图;需求里出现「先做 A 再做 B,若失败则走 C」,用活动图。状态机擅长表达生命周期和模式切换,活动图擅长表达功能流程和操作编排。

活动图里的动作节点,必须有归属。每个动作节点在模型库里要能对应到某个块的操作或接口方法,比如「同步引擎.启动上行同步」。「清洗」和“分析”这样的动作名不允许出现在活动图里,因为它在设计上无法追踪,在实现上也无法对应到具体模块。画活动图时,我会把每个动作节点的右侧注释写上负责块的名称和调用接口,这样设计评审时可以直接指着图问对应模块负责人:这个动作在你那边叫什么名字。

状态机图的建模约束更硬。状态迁移必须写监护条件,格式用「事件[条件]/动作」来表述。比如「网络恢复[同步队列非空]/开始同步传输」。不带监护条件的迁移在仿真时会退化成一张全连接图,状态任意跳转,死锁根本没法定位。我踩过一次这样的坑:某项目状态机图上迁移箭头齐全,但没有任何条件,团队仿真跑了很久都复现不出问题,最后发现是模型本身允许了物理上不可能的状态迁移,条件根本没被建模,仿真结果自然毫无意义。

行为建模还有一个容易忽略的维度:时间与时序。SysML 里有时序图,但它不是主角,真正的关键是把时间约束写在状态机和活动图的约束属性里。比如「断网缓存必须在 30 秒内完成本地落盘」这条约束属性,挂到活动图对应的缓存动作上,之后在参数仿真里就能验证是否满足。只画图不写约束,行为建模就还是停留在画流程图的水平。

3. 最小建模闭环:五张图串起一条可验证的需求链

3.1 建模顺序决定返工量:需求、用例、块、行为、参数的推进节奏

建模最忌讳拿到一条需求就立刻画块图。顺序错了,后面每补充一个视图都会推翻前面的图。我现在常用的推进顺序是:需求树 → 用例图 → 块定义图 → 内部块图 → 活动图/状态机图 → 参数图。这个顺序意味着前一层模型稳定程度至少要达到「评审通过」,才允许往下推进一层。遇到需求模糊的时候,只往下走半层,把不确定的部分用标记属性打上「待澄清」,不要为了补全而继续深挖。

只要顺序顺着走,返工量是很小的。举一个典型路径:需求图里有一条「RQ-02 离线状态可读本地缓存」,它通过 satisfy 关系挂到块定义图里的「本地缓存」块;本地缓存的读写行为由活动图表达;活动图里的缓存落盘动作顺便产生一个打开「缓存写入耗时」约束属性,最后参数图对该约束做计算验证。这一条链路走完,需求就不是一句口号,而是有实体、有行为、有参数校验的设计。

另一个纪律是模型库里的块只能定义一次。同一个块在 BDD 里定义,在 IBD 里以引用方式出现,不能在每张需要它的图里都复制一个同名块。复制出来的块在模型库里是两个独立对象,起初标识码不同没人注意,等需求追踪矩阵一生成,出现两个同名元素指向不同接口,整个模型的可信度立刻崩掉。我在建模工具里习惯把右侧模型浏览器中的块直接拖到图中生成引用,绝不手动复制节点。

3.2 同一个模型里组合块与状态:一张可直接运行的最小脚本

下面这段文本建模脚本把块、端口、状态机放在同一份模型描述里,适合用轻量文本建模工具快速跑通。它不是一个完整 SysML 工程,但能把整套思路里最核心的元素关系讲清楚。

@startuml title 最小设计闭环:离线缓存同步链路 block "同步引擎" as engine { state "空闲" as s_idle state "同步中" as s_sync state "等待重试" as s_retry } block "本地缓存" as cache { port "读取" as p_read port "写入" as p_write } block "云端接口" as api { port "上行数据" as p_upload port "下行确认" as p_ack } engine --> cache : 调用 engine --> api : 调用 s_idle --> s_sync : 网络可用[同步队列非空]/开始上传 s_sync --> s_idle : 同步完成[全部确认]/清空队列 s_sync --> s_retry : 失败[可重试]/计数加一 s_retry --> s_sync : 重试[计数小于3]/重新上传 @enduml

脚本的逻辑说明分三层看。第一层是结构关系:engine --> cache和engine --> api表示同步引擎同时依赖本地缓存与云端接口,但依赖方向不同,一个是读写本地数据库,一个是与云端建立连接。第二层是端口体现:cache 和 api 上各自定义了两个流端口,实际建模时还要给端口绑定数据类型,比如「读取」端口的类型是字节流,「上行数据」端口的类型是封装后的消息结构。第三层是行为约束:状态机挂在同步引擎这个块名下,迁移条件里既有事件又有「[条件]」,比如「同步完成[全部确认]」表示必须收到全部确认消息才允许回到空闲态。

参数上有两个建议值值得注意:一是重试上限设成 3,这在实际系统里意味着最多尝试三次上传,三次失败后进入人工介入流程;二是「同步队列非空」这个条件必须由活动图里实际产生的队列写动作来置位,否则状态机永远进不了同步态。跑这段模型时如果发现状态从「空闲」无法迁移到「同步中」,优先检查条件属性与动作节点之间的绑定关系,多半是队列变量没有被任何活动图动作更新。

3.3 需求追踪矩阵:每条需求最后必须落在一个验证活动上

模型建得再漂亮,没有追踪矩阵就等于白建。需求追踪矩阵不应该是额外维护的一份 Excel,而应该从模型库导出。SysML 的 satisfy 关系和 verify 关系直接决定了矩阵里「实现设计元素」和「验证活动」两列的内容;导出后按需求编号排序,凡是验证活动为空的行,都是模型库里还没闭合的需求。

下面这个需求追踪矩阵是命名和字段的参考形式。

需求编号需求内容实现设计元素验证活动当前状态
R-SYS-CLT-001App 在断网时仍可读取本地缓存中的历史记录本地缓存块 + 缓存读取活动图断网读取用例仿真已批准
R-SYS-CLT-002网络恢复后自动启动增量同步同步引擎状态机状态机仿真场景评审中
R-SYS-SRV-001服务端需在 5 秒内确认同步任务接收任务队列块 + 同步接口动作接口压力测试已冻结

矩阵导出之后,每周评审第一个就要看「验证活动为空」的行。这一行只要存在,需求就不能标记为已冻结。我习惯把矩阵的导出动作绑定到模型基线建立之前,每次建基线自动跑一遍导出,任何新增需求没有验证活动时,基线创建直接被阻断。模型库里的状态属性和这层阻断机制配合,才真正形成了「需求-设计-验证」的闭环,而不是把追踪表当成事后整理的文档。

4. 选工具与搭配置:把模型仓库变成多人协作的单一事实源

4.1 三类 SysML 工具各解决什么问题:从重型平台到轻量文本建模

从业者接触到的建模工具大致分三类。第一类是重型系统工程平台,通常把需求管理、建模、仿真、报告生成放在一个环境里,团队配置、审查、基线管理也是自带功能,适合大型系统项目和多专业联合设计。第二类是开源插件式编辑器,它寄生在通用开发环境里,核心能力是画 SysML 图和做基本的模型库查询,免费、上手快,但需求管理和多用户并发控制往往比较弱,适合小团队或项目早期探索。第三类是轻量文本建模工具,写脚本生成图形,适合画草图、快速迭代和放进 Git 做差异比较,但没有真正的模型库语义。

三种工具的适用边界要分清楚:文本工具快速、适合讨论,但字段的语义和完整性很难校验;重型平台完整、适合落地,但学习成本和配置成本高,不适合只画三张示意图就走人的项目。我的选择习惯是:小团队起步用文本工具验证建模方法,确认这套流程能带来实际决策价值后,再搬到主流建模平台维护正式模型。反过来先上重型平台、空转三个月画不出有效图的情况,我见过太多次。

下面这张对比表是从选型决策角度整理的,不涉及具体产品名称。

工具类型需求管理行为仿真多人协作建设成本适合阶段
重型系统工程平台强强强高型号研制、多专业协同
开源插件式编辑器弱部分支持弱中小团队、方法验证
轻量文本建模无无依赖版本库低草图、原型、接口讨论

4.2 多人建模最小配置:目录、命名约定和基线规则

模型库的目录结构直接决定协作效率。我的习惯是按视角分目录,而不是按模块分目录,因为需求、结构、行为三类模型会被不同的专业反复引用。下面是一套最小目录配置。

项目模型库/ ├─ 01_需求/ │ ├─ 需求图_系统级.puml │ └─ 需求追踪矩阵.csv ├─ 02_用例/ ├─ 03_结构/ │ ├─ BDD_系统组成.puml │ └─ IBD_同步链路.puml ├─ 04_行为/ │ ├─ 活动图_缓存读取.puml │ └─ 状态图_同步引擎.puml ├─ 05_参数/ └─ 99_基线/

目录之外,命名约定是第二层约束。文件命名统一用「图类型_对象名」的格式,图类型可选 BDD、IBD、活动图、状态图、需求图、用例图,这样按文件名排序时同类模型会聚在一起。模型内部元素命名统一用「模块名.对象名」,比如「同步引擎.启动上传」。「模块名.对象名」这种两段式命名从源头上避免同名冲突,也让需求追踪矩阵导出后可读性更强。

多人协作时最需要保护的是公共结构类元素,比如 BDD 和需求树。我一般规定同一时刻只有一个人能编辑这些公共目录,其他成员只允许往自己的专业目录里加引用。版本库上可以设置锁定目录,但没有必要把整个模型库锁死,否则协作效率太低。基线规则也简单:每周评审前取一次基线,基线里记录模型库版本号和导出的需求追踪矩阵,任何评审中提出的修改必须在下次基线建立前完成,模型库绝不保留长期无人认领的未冻结版本。

4.3 交换、合并与基线:XMI 导出和文本格式为什么都重要

SysML 模型在不同工具之间交换,依靠的是 XMI 标准格式。但 XMI 有一个实际问题:可读性极差,文件里全是节点 ID 和引用关系,人工看等同于看天书。不同工具导出的 XMI 在细节上经常存在兼容性差异,导过去以后图还在,但约束属性、端口方向定义这类信息容易丢失。所以我的习惯是:工具间交换用 XMI,团队内部评审看原生模型,涉及版本控制时优先保留一份文本格式脚本。

文本格式的价值在合并冲突时体现得很明显。XMI 文件如果进了版本库,两个同事同时改一条连接线,合并时几乎必然产生冲突,且冲突内容根本读不懂。文本建模脚本则不同,它每一行就是一个元素或关系,冲突时可以直接看是哪两个对象撞了,人工解决成本低得多。我见过的稳定做法是:主模型库收录正式模型文件,同时要求每个关键图的源脚本提交到版本库,模型与脚本保持同步。这样既保持了建模平台的语义完整性,又获得了文本格式的可审阅、可合并能力。

配置版本控制时还有三个细节容易被忽略。第一,模型库里不要直接编辑共享文件再提交,一定要通过工具打开、修改、保存这条链路,否则元数据丢失。第二,文件夹锁定期限不要设置太长,锁超过一天又没有说明,其他人就会被卡住,协作节奏立刻变慢。第三,每次基线建立后,把需求追踪矩阵导出成 CSV 放到「99_基线」目录里,这是评价这周建模质量的最直接证据,比看模型库里的图数量靠谱得多。

5. SysML 建模避坑:5 个画了也白画的常见问题

5.1 需求图画成思维导图,关系语义全部丢失

现象:需求图上每个节点都有,节点之间也用线连起来了,但所有线都没有方向,也看不出是 derive 还是 satisfy。评审时没有一个人能说清楚这条需求到底由谁实现,一张图从设计工具变成了宣传海报。

原因:很多人在操作时直接拖了「连线」而不是「关系」,或者建完关系后没有选择类型,工具默认给了无方向连线。画图的时候看不大出来,但导出需求追踪矩阵时,这些关系因为缺少语义类型,根本不会被统计进去。

解决:每次连接需求节点前先选关系类型,我的习惯是至少把 derive 和 satisfy 用颜色区分开,derive 用虚线、satisfy 用实线。画完后在工具里导出一张关系表,重点检查有没有两个需求互相之间同时存在派生和满足关系,那是建模时典型的循环引用错误。每周评审先把这组关系过一遍,需求图才真正具备「可在模型库里被追踪」的价值。

5.2 内部块图只画连接线不挂端口,接口还是靠脑补

现象:内部块图看着很充实,线一条不少,但每个块边缘都没有端口标识,流方向更是看不出来。评审现场有人问这条线是不是数据流,有人说是控制信号,结论完全靠猜。

原因:端口在图面上比连线多一层单独的操作,新人不熟悉就会自动跳过,图先画出来再说。但接口错误往往就是这一步省出来的,两端块上的端口名不一致,模型里根本不会报错,直到联调才发现两个模块对接口的定义是两套。

解决:在建模工具里先配置端口类型库,标准端口、流端口、方向全部定义好。然后执行一条硬规定:内部块图上的连接必须从端口引出,禁止块边缘直接拖线。这条硬规定靠人自觉很难维持,建议在建模工具或团队规范层面做成强制校验项,无法通过校验的图不允许纳入基线。

5.3 状态机迁移不写监护条件,仿真一跑全是死锁

现象:状态机图所有迁移箭头都存在,状态互相都能到达,看起来覆盖很完整,可一跑动态仿真,要么状态乱跳,要么卡在某个状态出不来。

原因:迁移上没有 guard 条件,也没有事件。模型允许了大量物理上不可能发生的迁移路径,比如设备还在断电状态就直接进入同步中,仿真当然得不到任何有意义的结果。

解决:每个迁移必须按「事件[条件]/动作」三段式填写。我通常从需求原文里抄触发词,需求写「网络恢复后自动启动」,迁移就写「网络恢复[同步队列非空]/开始上传」。跑仿真前先静态检查一遍,凡是迁移条件为空的全部高亮标出,逐一补齐再执行。

5.4 活动图一张图塞 20 个动作,行为无法赋予到块

现象:活动图页面上密密麻麻排了 20 个动作节点,一个动作节点里还写了四五行说明,图放大才能看清。后期想改一个流程分支,牵扯到十多个动作,所有人都不敢动。

原因:活动图没有做分层,也没有用调用行为动作。图上的动作既没有归属块,也没有粒度控制,画到哪算哪,最后变成一张巨型流程图。

解决:一个活动图的动作数控制在 9 个以内,超过就拆分。对于重复出现的行为,用调用行为动作去引用已定义好的子活动图,而不是复制一份新的动作节点。同时给每个动作节点的属性里填好所属块和调用接口,写不出来的动作说明粒度太粗或需求本身有问题,应该先回到需求图去补清楚,而不是在图上硬画。

5.5 建模节奏失控,模型库建了一堆图却建立不了基线

现象:团队初期热情很高,一个月画了几十张图,需求、结构、行为铺得都很满,但每次评审拿不出一个「完成」的模型,每张图上都有未冻结的元素,需求追踪矩阵里大量行没有验证活动,建模工作逐渐变成负担。

原因:建模范围没有和需求的冻结范围绑定。大家为了图完整,把不确定的需求也一起建模,导致所有图都处于半永久进行时状态,不知道哪些能进基线、哪些不能进基线。

解决:把整个项目需求按重要度切成一到三个子集,先冻结一个很小的子集,把这个子集对应的需求图、块图、行为图、参数验证全部走完,形成第一条完整闭环。第一条闭环做出来之后,再把下一个需求子集放进来。评审之前检查所有模型元素的状态属性,未冻结元素不允许进入基线。

6. 把模型从静态图推向可执行仿真:状态机与参数校验的最后一公里

建模做完整套闭环之后,剩下最后一步是把模型变成可执行的东西。我的建议是不要一上来就做全模型动态仿真,先挑出最危险的三个状态场景。所谓最危险,是那些发生频率不高、但一旦发生会导致系统进入不可恢复状态的分支,比如「同步失败重试次数耗尽」「断网瞬间收到删除指令」「缓存满时仍在写入」。给这三个场景各建一个仿真用例,输入序列按照状态机的迁移条件构造,仿真跑完以后第一件事不看到没到终点状态,而是看覆盖率报告里哪些状态没有被访问到。没有执行到的状态,往往是需求描述里最含糊、团队成员理解最不一致的地方,它们就是下一轮建模的重点。

参数校验不用等仿真工具,常见做法是在参数图里加约束块,把关键指标写成不等式。比如同步接口的最坏情况存储估算:缓存容量等于单条记录占用空间乘最大离线记录数,再加上重试阶段的任务队列占用。把这条不等式写进约束属性后,每次修改缓存策略或同步频率,模型库都能自动算一遍约束是否仍被满足。这比靠人力去翻计算书更新快得多,也能在需求变更时立刻暴露参数之间的连带影响。

我自己的习惯是画完任何一张图都会先问一句:如果不画这张图,评审会漏掉哪个接口、哪条需求、哪个异常分支?回答不上来的图,当场删掉。模型库宁可范围小,也不能让每张图都变成只能用来播放展示的示意图。那一次状态机仿真出来一堆死锁却找不到问题之后,这个「删图」的习惯就成了我每次建模前的第一道检查。希望这些方法能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进