UML通信图实战:消息编号、布局与架构评审
通信图这个词在不少团队的评审会上都出现过但真正能把它画对、用对的人并不多。大多数人第一次接触它是在翻建模规范时看到“通信图Communication Diagram是交互图的一种强调对象之间的连接关系”——然后就翻过去了。可等到自己手上有一个七八个对象互相调用的链路顺序图画出来横向拉了三屏、消息箭头密密麻麻像地铁线路图的时候才会想起还有这么一张图能救命。它解决的问题很具体当你想看清楚“谁跟谁有连接、消息沿着哪条链路走、编号背后是什么调用层级”时通信图比顺序图更省纸、更省眼。这篇内容面向三类人写设计文档的开发者、做代码评审的技术负责人、以及正在准备建模相关材料的学习者。我会从它到底解决什么问题讲起一直讲到编号怎么写、布局怎么排、工具怎么选、哪些坑必须绕开全部基于我在实际项目里画废几十张图之后的经验。1. 通信图真正解决的痛点顺序图横向失控之后1.1 一条业务链路被顺序图拉成三屏时问题就来了我印象最深的一次是做一个票务后台的下单链路设计。参与者有前端网关、下单服务、库存服务、价格计算、优惠券、支付预校验、消息投递一共七个对象消息数量二十出头。当时用顺序图画出来横向七条生命线纵向二十多行导出成 PDF 之后 A4 纸放不下缩放之后字小到看不清。更麻烦的是评审的时候大家都在找“库存服务到底被谁调了”眼睛要在七八条竖线之间来回扫扫半天才能定位到那条跨越五条生命线的长箭头。这不是顺序图本身的错。顺序图的强项是时间维度它把“先发生什么、后发生什么、什么时候返回”表达得非常清楚纵向阅读符合人的直觉。但它的代价是空间浪费严重——每一个参与者不管参不参与当前这段交互都要占一条贯穿全图的竖线。参与者一多横向就被撑爆了。而实际情况是这七个对象里有一半彼此之间根本没有直接连接它们的存在只是为了表达“这个交互涉及这些角色”。通信图换了个思路既然是“谁跟谁说话”的问题那就别用竖线了直接把对象摆成一个个节点用线把它们连起来消息挂在线上顺序用编号表达。同样七个对象、二十条消息通信图画出来的面积大概是顺序图的三分之一而且“库存服务被哪三个对象调用”这个问题看一眼节点上连出去的线就有答案了。这个视觉上的差异不是审美问题是阅读效率问题——尤其是在评审场景下别人的注意力只给你三分钟。1.2 它的本质是“空间拓扑优先时间顺序靠编号补位”理解通信图最关键的一句话是它把交互拍成了一张俯视图而不是一段录像。顺序图是录像你从上往下看时间在流动通信图是俯视图你看到的是对象之间的静态连接结构时间信息被压缩成了一串编号。这个设计取舍带来两个直接后果。第一个后果是连接关系被提到了第一等公民的位置。图上每一条线规范里叫“链”link都代表两个对象之间确实存在一条可以传递消息的通路这条通路在实现层面通常对应一个引用、一个句柄、一次依赖注入或者一个方法参数。也就是说通信图天生就在逼你思考耦合问题——你画的每一条线都是一处需要维护的依赖。第二个后果是顺序信息被外包给了编号。这是通信图最容易被低估、也最容易画错的地方。图上没有上下之分如果编号写得不严谨读者根本还原不出调用时序。我见过太多图消息编号要么从 1 编到 20 一条直线下来完全丢掉了嵌套调用的层级要么编号断断续续1、2、4、7中间缺失的部分没人知道发生了什么。编号是通信图的骨架这条后面单独用一节讲。顺带说个历史细节很多人不知道通信图以前不叫这个名字。在较早的建模规范版本里它叫“协作图”Collaboration Diagram后来改名为通信图理由是“协作”这个词太容易被和“协作行为”“团队协作”混淆而这张图真正表达的是对象间的通信。如果你翻到一些老资料或者老项目文档看到协作图别以为是另一种图它就是同一个东西换了名字。有些老工具里至今还保留着旧的叫法菜单里找不到“通信图”的时候可以找找这个。1.3 什么时候我会优先掏出通信图不是所有场景都适合通信图用错了反而添乱。我自己的判断标准大致是三条按优先级排场景特征推荐图种理由参与者不超过 4 个重点是时序顺序图通信图的编号成本不划算顺序图更直观参与者 6 个以上连接稀疏通信图空间利用率高拓扑关系一眼可见重点要评审“谁依赖谁”通信图链就是依赖天然暴露耦合重点要评审“异常路径、超时、重试”顺序图时间维度的细节通信图表达起来很吃力要和产品、测试对齐用例步骤顺序图从上往下读和用例步骤一一对应要写进架构文档说明模块边界通信图一张图说清模块间的调用扇出第三条尤其值得展开。通信图在架构评审里的价值往往被严重低估。你画一张通信图如果某个对象身上连出去七八条链那基本就是个需要拆解的“上帝对象”如果两个本该独立的模块之间画了一条链那说明边界破了。这些问题在顺序图里也能看出来但没那么扎眼——顺序图里一条横箭头只是众多横箭头中的一个而通信图里一个节点长满触手视觉冲击力是完全不同的。我个人在做架构梳理的时候习惯先把核心链路画成通信图专门用来看扇入扇出然后再对关键路径补顺序图。两张图配合用比只画一种要有效得多。2. 图上的元素不多但每个都有语义陷阱2.1 对象框、链、以及“链不等于关联”这条界线通信图的元素清单短得有点出人意料对象框、链、消息大概就这三样再加上一些编号修饰符。但越是简单的东西语义边界越容易糊。先说对象框。它和对象图里的对象框长得一样写法是对象名:类名两部分都可以省略其一。如果是集合或者多个同类实例参与会在框的左上角画成叠起来的两个矩形表示多对象。如果是主动对象也就是自己拥有控制流的对象框的边框会加粗或者两侧加竖线。这些细节在纸质文档里经常被省掉但在需要精确表达的时候不能省——尤其是主动对象标记它直接影响读者对“谁在驱动这次调用”的判断。再说链。链是连接两个对象的那条实线它在语义上表示“这两个对象之间可以传递消息”。这里有一个非常常见的误解链不等于关联。关联是类层面的静态关系说的是“这两个类在整个系统生命周期里存在结构性联系”链是实例层面的、一次交互范围内的临时通路。举个例子下单服务在处理方法里接收了一个库存服务的参数它们之间在这次交互中存在一条链但这两个类之间未必有长期关联关系。很多人在画通信图时把类图上的关联关系原样搬过来画出一张“关系大全”然后问为什么消息发不出去——因为那些线表达的是静态结构不是本次交互的通路。还有一个细节是链的方向。链本身在多数情况下画成无向实线因为连接是双向可通的。消息箭头才是有方向的。但如果你要表达的是单向可见性比如 A 持有 B 的引用而 B 完全不知道 A 的存在那可以给链加上导航箭头。这个做法不算错但会让图面变脏我的习惯是只在“单向可见性本身就是设计重点”的时候才标注其他情况一律用无向链靠消息箭头表达方向就够了。2.2 编号才是这张图的隐性主角通信图里最考验功力的是消息编号没有之一。它同时承担了两个职责表达顺序和表达嵌套层级。顺序编号很简单就是 1、2、3 依次往下。但真实系统的调用是有层次的A 调用 B 的方法B 在处理过程中又调用了 C 和 D然后才返回。如果用平铺的 1、2、3、4 来编号读者完全看不出后两个消息是从第一次调用里派生出来的。规范给出的解法是点号分层编号1: 创建订单 2: 校验库存 2.1: 查询可售库存 2.2: 锁定库存 3: 计算价格 3.1: 获取基础价 3.2: 应用优惠规则 3.2.1: 查询可用券 3.2.2: 计算折扣 4: 生成支付单这串编号读起来就是一棵调用树。3.2.1意味着它是3.2内部发起的调用而3.2又是3内部发起的。你甚至不需要看图上的布局光看编号就能还原出调用栈。这一点在评审时特别有用——我把编号列出来贴在文档里不用图也能讲清楚链路。编号的另一个隐性作用是暴露“返回消息的省略是否合理”。通信图里的返回消息是可选的大多数情况下省略不画因为返回是隐含的。但如果某个调用有重要的返回值需要强调比如“库存校验返回了不足的数量”那就要显式画一条回程箭头编号可以是2.3或者用返回值的形式标注2: 校验库存() → 可用数量。我个人的规则是只有当返回值影响后续分支判断时才画回程否则一律省略保持图面干净。2.3 条件、迭代、并发三个修饰符怎么写才不歧义光有编号还不够真实逻辑里有分支、有循环、有多线程。通信图用三个修饰符来覆盖语法不难但写法不统一。条件用方括号包起来放在编号后面2 [库存充足]: 锁定库存。这表示这条消息只在条件成立时发送。这里要注意一个坑条件写在哪里决定了它的作用范围。写在编号后面的条件只作用于这一条消息如果你想让一组消息共享同一个条件得把它们统一成同一个编号前缀下的子编号然后在父级标注条件比如2 [库存充足]:下面挂2.1、2.2。我见过把条件写在链上的写法那是错的链是静态的没有执行条件这回事。迭代用星号加循环变量表达3 *[对每个商品项]: 计算小计。方括号里可以写循环范围比如*[i : 1..n]。这里的语义是“这条消息会被重复发送多次”。有个细节值得提醒迭代标记和条件标记可以叠加3 *[对每个商品项][数量 0]: 计算小计意思是循环中跳过数量为零的项。这种叠加写法在规范里是允许的但可读性会下降如果逻辑复杂到需要两个修饰符叠加我建议直接拆成两条消息或者在图旁加注释说明。并发用后缀字母表达2.1a和2.1b表示这两条消息是并行发出的可能在不同线程上。这个写法在书面上没问题的但在实践中有个尴尬的事实——大部分建模工具对并发后缀的渲染支持很差要么不认这个语法要么渲染出来和普通编号没区别读者根本看不出并发。我的折中做法是图面上用字母后缀同时在图下方的说明文字里明确写“2.1a 与 2.1b 并行”双保险。2.4 和顺序图之间能不能无损互转理论上通信图和顺序图是同构的表达的是同一份交互信息只是投影角度不同。所以理论上可以互转。实践中也确实有工具提供互转功能。但我要泼一盆冷水工具层面的互转大部分情况下会得到一张能看但不好看的图。原因在于布局。顺序图转通信图工具不知道该把哪个对象放中心往往是按出现顺序从左到右排结果画出一条长长的链条链交叉得一塌糊涂通信图转顺序图工具会按照编号顺序排列生命线但因为原始图里没有明确的“谁先创建”的信息经常出现参与者在错误的位置被激活。我的实际做法是手动画两遍而不是依赖互转。听起来费事但有个额外收益画第二遍的时候你会被迫重新审视一遍交互逻辑经常能发现第一遍漏掉的异常分支。有次我把一张通信图转成顺序图画到一半发现“库存锁定失败”这条回滚路径在原图里根本没有对应消息补上之后才发现代码里确实漏了补偿逻辑。这种收获是自动互转给不了的。3. 从一段口头需求到一张能看懂的图3.1 起点不是画图是把参与者和链列成表我见过不少人拿到需求就直接开工具拖框拖到一半发现对象找错了、链连错了推倒重来。效率最高的起点其实是纯文本先列两张表。第一张表是参与者表三列对象名、类名、角色说明。角色说明这一列很多人会偷懒不写但它是后面判断“该不该画进这张图”的依据。比如“缓存”这个对象如果它只是内部实现细节对当前交互的语义没有影响就不该画进来如果它的命中与否会改变链路走向那就必须画。第二张表是消息表五列编号、发送方、接收方、消息名、修饰符条件/迭代/同步异步。这张表是通信图的完整数据源画图本质上就是把这两张表可视化。我在实际项目里的顺序是消息表先写完整编号先按调用顺序捋一遍再回头调整层级编号最后才打开工具。这个“先表格后图形”的习惯最大的价值在于它让修改成本变得极低。评审时有人说“这里少了一个校验”你改表格一行图上加一个框一条线如果一开始就画图改起来要重新布局。而且表格本身就是可以放进设计文档的东西比图更容易做版本比对。3.2 编号策略层级编号优先顺序编号只在小链路用前面讲过层级编号的写法这里讲策略选择。参与者不超过 4 个、消息不超过 8 条的小链路用平铺的顺序编号 1、2、3 就够了硬套层级编号反而显得过度设计。但只要出现“调用中嵌套调用”的情况立刻切换到层级编号。判断标准很简单如果 A 调用 B 之后B 还要调用 C而 A 并不知道 C 的存在那这就是嵌套必须用层级编号否则读者会误以为 A 直接调用了 C。层级编号有个副作用是数字增长很快一个中等复杂度的链路编号能到4.3.2.1这种四层。这时候可读性会下降。我的处理办法是控制嵌套深度一般不超过三层。如果发现要写到第四层说明这段交互的抽象层级不够应该把中间那层合并成一个更高层的消息或者拆成两张图分别表达。还有一个实操技巧编号里的整数部分保持连续不要跳号。1、2、3、4中间不要出现空缺。子编号可以按需增减但父编号必须连续。这样读者看到3和5之间缺了4立刻就知道图不完整或者漏了东西。我审图的时候第一步就是扫一遍顶层编号有没有断断号往往意味着有人在改图时删了消息但忘了重排。3.3 布局中心辐射优于从左到右交叉线必须消灭通信图的布局对可读性的影响比顺序图大得多。顺序图的布局是被约束死的生命线顺序 时间向下你没什么发挥空间通信图的布局完全自由自由就意味着画坏了会很糟。我的布局原则有三条按优先级第一找出扇出最大的对象放中心。通常是那个“编排者”角色比如下单服务、调度中心、网关。把它放画布中央其他对象围绕它排布。这样大部分链都是短距离的辐射线不会横穿整个画布。反过来如果按调用顺序从左到右排编排者往往在左边所有调用都要往右延伸画出来就是一张蜘蛛网。第二强关联的对象放邻近位置。如果 A 和 B 之间有三条消息来回那它们必须在图上挨着否则三条线平行拉长视觉上会误以为是三条不同的通路。这一点工具不会帮你判断只能手动调。第三交叉线一律消灭消灭不了就换布局。通信图里两条链交叉交叉点会被误读成“这两个对象也相连”。这是最致命的可读性问题比顺序图里的箭头交叉严重得多。实在消不掉的时候我的做法是在交叉处加一个小的跨线弧或者在链上标注编号让读者能区分——但说实话更好的办法是重排节点位置八成情况下换个布局就解决了。3.4 一个完整示例下单链路怎么落到图上拿前面提到的票务后台举例简化成五个对象网关、下单服务、库存服务、计价服务、消息投递。消息表大概是这样的编号发送方接收方消息修饰符1网关下单服务提交订单同步2下单服务库存服务校验并锁定库存同步2.1库存服务库存服务扣减可售数自调用3下单服务计价服务计算应付金额同步3.1计价服务计价服务应用优惠无4下单服务消息投递发送下单成功事件异步5 [库存锁定失败]下单服务消息投递发送下单失败事件异步落到图上下单服务放中心网关在左库存服务、计价服务在右上下错开消息投递在右下。网关到下单服务一条链标1下单服务到库存服务一条链标2库存服务上挂一个自环标2.1下单服务到计价服务一条链标3计价服务挂自环标3.1下单服务到消息投递一条链上有两个箭头分别标4和5 [库存锁定失败]。整张图五个框、六条链比顺序图紧凑太多。这里有个细节值得单独说同一个对象对之间的多条消息画在同一对链上还是分开画。规范允许多条消息箭头挂在同一条链上也允许画多条平行链。我的习惯是同一对对象之间只画一条链所有消息箭头都挂在这条链上用编号区分顺序。这样图面最干净。只有当两个对象之间存在多条语义完全不同的通路时比如一条是同步调用、一条是事件订阅才画成两条链。另外自调用对象给自己发消息画成一个小环回到自己身上不要画成一条从框出发又回到同一个框的直线那样看着像画错了。小环的位置我一般放在框的右上角编号标在环旁边避免和进出这个框的其他链打架。3.5 工具选型文本派和图形派各有各的坑工具这块我踩过的坑能写一整篇这里挑重点说。工具大致分两派。图形派是拖框连线的那种上手快布局自由度高适合需要精细控制版面的正式文档。坑在于版本管理——二进制文件没法 diff两个人同时改就冲突最后只能靠“谁最后保存谁说了算”。文本派是用纯文本描述图编译出图片好处是能进 Git、能 diff、能代码评审坏处是布局基本不受控复杂图的自动布局经常很难看。我的实际组合是正式交付物用图形工具精修日常评审和版本管理用文本描述。写设计文档的时候先用文本描述把结构定下来评审通过之后再在图形工具里排一次版导出成图片嵌进文档。关于文本派工具有个必须提醒的点不是所有基于文本的绘图工具都原生支持通信图的语法。有些工具提供了对象图的语法可以用对象框加带标签的箭头勉强模拟通信图但编号的层级缩进、并发后缀这些细节需要手写在标签文本里工具不会做语义校验。这意味着写错了不会报错只能靠人眼检查。我建议是写完先自己用编号表核对一遍别指望工具兜底。图形派工具里要看它是否支持三样东西多对象框叠矩形、主动对象标记、并发后缀渲染。这三样是通信图区别于普通流程图的关键很多轻量级工具只支持前两样并发后缀一渲染就丢。选工具之前花五分钟验证这三样能省掉后面返工的时间。4. 那些年画错的图几个高频翻车点4.1 画成了对象关系图一条消息都没有这是最典型的一种。图上有七八个对象框线连得整整齐齐但线上空空如也一个箭头、一个编号都没有。作者的解释通常是“我想表达这些对象之间的关系”。问题在于那样的图是对象图或者类图不是通信图。通信图的定义里消息是必须存在的核心元素——没有消息这张图就没有存在意义。我判断一张图是不是通信图第一眼就看有没有带编号的消息箭头。这个错误的根源往往是概念混淆把“链”和“关联”当成一回事觉得把线连上就表达完了。实际上链只是消息的载体没有消息的链在这张图里是冗余信息。如果你的目的确实是表达静态结构那就该去画对象图如果是表达交互那每条链上至少得有一条消息。4.2 编号断裂、跳号、层级混乱这类问题的出现率仅次于上一条而且更隐蔽因为图看起来是“完整”的只是编号有问题。我整理过几种典型症状。第一种是跳号1、2、4、5缺了 3。这通常是改图时删了消息忘了重排读者会以为漏了一页。第二种是层级塌陷1、2、3、4、5 全是顶层编号但实际逻辑里 3 和 4 是 2 的子调用。这种图读起来顺序对但嵌套关系全丢了实现的人会误以为四个调用是平级的。第三种是层级错位2.1挂在1下面但发送方明显是2的接收对象。这种一般是改编号时手滑把父编号写错了。检查办法很土但很有效把编号按字典序列出来逐条核对发送方和接收方是否和父编号对应的对象一致。父编号的接收方应该就是子编号的发送方或者它的下游不一致就是错的。这个检查我每次画完图都会跑一遍大概能抓出八成问题。4.3 条件和迭代写在了错误的位置修饰符的位置错误有三个高发点。一是把条件写在链上。链是静态连接没有“条件成立才连接”这回事条件永远属于消息。二是把迭代写在编号前缀外面写成*3: 计算正确写法是3 *[范围]: 计算星号在编号后面。三是条件的作用范围搞错把本该作用于一组消息的条件只标在了第一条消息上导致读者以为只有第一条受条件约束。第三种最麻烦因为它不报错、不显眼但会误导实现。我的规避方式是只要一个条件会影响两条以上的消息就不在消息上标而是在图下方写一段说明明确“编号 3 及其所有子编号在条件 X 成立时执行”。文字说明比图上的修饰符更不容易被误读。4.4 把实现细节塞进设计图这个坑不是语法错误是层次错误但对图的伤害最大。具体表现是图上出现getConnection()、setTimeout(3000)、close()这种纯实现层面的消息或者出现数据库连接、线程池、序列化器这类基础设施对象。结果是图变得极其臃肿原本五个对象能说清的事画了十五个对象读者抓不住重点。我的判断标准是这条消息如果换一种实现语言就会消失那它就不该出现在设计层的通信图里。getConnection()换个框架就没了删掉校验库存换个语言也还在保留。基础设施对象同理除非它的行为会影响业务链路的走向比如缓存未命中导致要回源否则一律不画。如果确实需要表达实现细节正确做法是分层设计层画一张精简的通信图实现层再画一张详细的两张图各自标注清楚适用范围。混在一张图里两边都做不好。5. 这张图在协作里到底扮演什么角色5.1 扇入扇出会自己说话耦合藏不住通信图最被低估的价值是它天然是一张耦合热力图。你不需要额外做任何分析只要把图画出来扇出一个对象连出去多少条链和扇入一个对象被多少条链连进来就摆在那里。扇出超过五的对象基本可以判定是职责过重值得单独拉出来讨论要不要拆。扇入特别高的对象通常是共享组件要考虑它变更时的影响范围。我在做存量系统梳理时有个固定动作把核心链路画成通信图然后数每个节点的度数。度数最高的那两三个节点往往就是系统里最难改、最容易出故障的地方。这个结论不需要任何工具支持肉眼数线就行但它的准确率出奇地高。需要注意的是这个判断只在“同一抽象层级”的图上成立。如果图里混了业务对象和基础设施对象度数的意义就失真了——一个日志组件被所有对象连接是正常的不能据此说它职责过重。所以前面强调的层次一致性在这里又出现了一次。5.2 评审时我通常只问三个问题图拿出来之后评审的时间往往很短。我总结下来三个问题能覆盖大部分风险第一个问题每条链在代码里对应什么。如果画图的人答不上来说明这条链是臆想的实现时大概率对不上。这个问题能揪出“设计图画得漂亮但落不了地”的情况。第二个问题编号里有没有异常分支。通信图的编号天然偏向正常路径失败、超时、重试这些往往被省略。问一句“库存锁定失败了走哪条”如果图上找不出来那就是缺失。这一点上通信图确实不如顺序图直观因为顺序图可以用一个明显的 alt 框把分支框起来通信图只能靠条件修饰符容易被忽略。第三个问题有没有哪个对象是被“顺手”画进来的。这个问题专治臃肿。被问到的对象如果解释不清楚它在这条链路里的作用就该删。三个问题问完图的质量基本就有了判断。这套问法我在不同项目里用了很多次比逐条核对消息有效得多。5.3 图的生命周期该更新就更新该删就删最后一件事关于图的管理。通信图和代码一样会腐化。改了实现没改图过半年这张图就成了误导。我的做法是给每张图标注两个信息对应的代码模块路径和最后一次核对的时间。前者让人能找到源头后者让人知道这张图的可信度。另一个经验是不要试图维护一张“大而全”的通信图。系统一大把所有交互画在一张图上必然失控。正确做法是按场景拆分一张图讲一条链路图与图之间通过对象名关联。这样做的好处是每张图都能独立更新某条链路改了只影响那一张不会牵一发动全身。至于什么时候该删图我的标准是当这张图表达的内容已经被更好的东西替代时。比如链路已经稳定运行很久代码本身就是最好的文档图只剩下考古价值那就该归档而不是继续维护。留着过期图不删比没有图更危险因为总会有人当真。6. 几个让我少走弯路的具体做法6.1 编号表先行图后置改动成本能降一个量级前面提过一次这里展开讲讲为什么这个顺序这么重要。画图的时候人的注意力在“框放哪、线怎么连”上对逻辑本身的审视会变弱。先写编号表注意力全在“谁调谁、什么顺序、有没有漏分支”上逻辑问题在这一步就能暴露。等表格没问题了再去画图画图就变成了纯粹的排版工作不会返工。我自己的实操是这样的拿一张纸或者一个纯文本文件左边写编号右边写“发送方 → 接收方: 消息”一行一条。写完之后通读一遍重点看三个地方顶层编号是否连续、每条子编号的父编号对应的接收方是否是本条消息的发送方、每个条件的作用范围是否明确。这三项过了再开工具。这个习惯带来的另一个好处是编号表可以直接贴进设计文档当成文字版链路说明。有些读者不爱看图看表格反而更快。6.2 一张图只讲一条链路别贪多新手最容易犯的贪心错误是想用一张通信图把所有交互都装进去。结果就是对象框密密麻麻链交叉成网谁也看不懂。我的原则是一张通信图对应一个用例或者一条主链路参与者控制在五到八个消息控制在十五条以内。超过这个规模就拆。拆的维度可以按业务阶段下单、支付、履约也可以按参与者边界内部链路、外部集成。拆图的时候要注意保留“接缝”信息。比如下单链路拆成两张图那第一张图的最后一条消息和第二张图的第一条消息之间要有明确的衔接说明否则读者不知道两张图怎么接上。我通常会在图下方写一行“本图结束于消息 4后续见 XX 链路图”。6.3 图下方的说明文字比图本身更能救命最后分享一个被严重低估的做法在图下方写三到五句说明。通信图的图面信息密度有限很多约束表达不出来。比如某个消息是异步的、某个调用有超时限制、某个对象是外部系统不能保证可用性——这些信息塞进图里会破坏版面写在图下方却恰到好处。我的说明文字通常包含三块内容图的适用范围这条链路对应哪个业务场景、关键约束超时、重试、幂等要求、以及图中省略了什么比如“省略了日志和监控相关的调用”。第三块尤其重要它能让读者知道哪些是故意不画的而不是漏画的。写说明文字还有个附带好处写的过程中经常会发现问题。我有次在写“省略了库存回滚”这句说明时突然意识到回滚逻辑其实不是省略是压根没设计。这种发现光看图是看不出来的。画图这件事工具和语法都是次要的真正决定一张图有没有价值的是画的人有没有想清楚这条链路里每个对象存在的理由、每条链背后对应什么依赖、每个编号背后是什么调用栈。把这些想明白了哪怕用最朴素的工具画出来图也是有用的想不明白工具再花哨也只是把混乱可视化了一遍。我自己的体会是通信图最好的用法不是画给人看而是画给自己看——在动手写代码之前先把脑子里那团模糊的调用关系摊平到纸上很多设计上的犹豫当场就有了答案。