时序图栏目 ·

时序图常见错误 20 条:为什么你画的“看起来对但就是不专业”(附修正)

时序图常见错误有哪些?本文从研发/测试/架构的读图习惯出发,总结 20 条最常见的 UML 时序图错误(参与者、消息、返回、激活条、组合片段 alt/opt/loop/par、异常与超时、幂等与重试等),每条都给出为什么不专业以及如何修正,并附检查清单与 FAQ。

很多人第一次画时序图(UML Sequence Diagram)都会遇到同一种尴尬:

  • 自己觉得“交互顺序都写清了”,但别人看完说“读不懂 / 不严谨 / 不像专业图”
  • 图里元素不少,但该强调的没强调,该省略的反而画满了
  • 一到异常、超时、重试、回调、幂等,图就开始失控

这篇文章不讲玄学,直接给你一份“时序图常见错误清单”:20 条高频错误 + 为什么不专业 + 怎么修正。你可以把它当成画图前的检查表,也可以当成评审时的对齐标准。

如果你需要快速把一段“文字交互”变成可读的时序图,可以用在线工具先把骨架搭出来,再按本文逐条检查: 手打时序图 - 时序图在线制作


先给结论:一张“专业”的时序图,最重要的不是画得多,而是“语义不歧义”

一张可交付的时序图,至少要做到三件事:

  1. 边界清楚:谁是调用方/被调用方?系统边界在哪里?
  2. 语义清楚:同步/异步、分支/可选/循环/并行,读者一眼能判定
  3. 失败清楚:失败怎么返回?超时怎么处理?是否重试/补偿?

下面进入 20 条常见错误。


错误 1:把“流程图”当“时序图”画(只有步骤,没有对象)

症状:图里全是“步骤 1/2/3”,但没有明确“谁对谁发了什么消息”。

为什么不专业:时序图的核心是“对象/参与者之间的消息交互顺序”,不是“操作步骤列表”。流程图讲的是步骤;时序图讲的是交互。

修正:先把参与者列出来,再写消息。

  • 用户/客户端
  • 网关/API
  • 业务服务
  • DB/缓存
  • 第三方

错误 2:参与者命名含糊(Service A / 模块1 / 系统X)

症状:参与者叫“系统A”“模块B”,读者不知道它到底负责什么。

为什么不专业:参与者命名是读图入口。命名不清,后面消息再清也会被误解。

修正:用“职责 + 角色”的方式命名,例如:

  • OrderService(下单服务)
  • PaymentGateway(支付网关)
  • RiskService(风控)
  • OrderDB(订单库)

如果确实是外部系统,标注 <<external>> 或在标题/注释里说明边界。


错误 3:生命线随意长短,甚至没有对齐时间轴

症状:有的生命线到一半就没了,有的拖到最底部;消息线也不对齐。

为什么不专业:生命线表示“对象存在并可能参与交互的时间跨度”。随意截断会让读者误判对象是否还在起作用。

修正

  • 一般参与者生命线贯穿该图范围
  • 如果对象确实在过程中创建/销毁,明确画 create / destroy(或用注释说明)
  • 消息严格按从上到下的时间顺序排列

错误 4:同步/异步箭头乱用(看不出到底谁在等谁)

症状:所有调用都用同一种箭头,或者把异步画成同步。

为什么不专业:同步/异步决定了“调用方是否阻塞等待返回”。这直接影响超时、并发、重试与资源占用的理解。

修正:至少在图里达成团队一致:

  • 同步调用:调用方等待返回(常见:HTTP/RPC)
  • 异步消息:调用方发出后不等待(常见:MQ/事件)

如果你们团队不想纠结箭头样式,也可以在消息文字上明确标注:sync / async


错误 5:把“返回值”当成“新的请求”画

症状:返回消息也画成实线请求,或者返回消息和下一次调用混在一起。

为什么不专业:读者会误以为发生了额外的调用,甚至误解为回调。

修正

  • 返回可以用虚线箭头表示(或省略,但要在关键处保留)
  • 返回消息文字写“返回什么”而不是“做什么”

一个简单规则:返回消息尽量用“结果/状态/错误码”的名词短语


错误 6:激活条(Activation)要么不画,要么画满整条生命线

症状:激活条从第一条消息一直画到最后;或者完全不画,读不出调用栈。

为什么不专业:激活条表达“控制权/执行上下文”。画错会让读者搞不清谁在执行、谁在等待。

修正

  • 同步调用:调用方激活条持续到收到返回
  • 被调用方激活条从收到请求开始,到返回结束
  • 嵌套调用用嵌套激活条(不要硬画成一条长条)

错误 7:把业务判断写成一段长文字,图里看不出分支

症状:在某条消息旁边写“如果 A 则…否则…”,但图里没有分支结构。

为什么不专业:读者无法明确“分支从哪里开始、哪里结束、互斥还是可选”。

修正:用组合片段:

  • alt 表达 if-else 互斥分支
  • opt 表达可选分支

并且写清 guard 条件:[token 有效][余额不足]


错误 8:alt/opt/loop/par 乱用,导致语义歧义

症状:所有复杂情况都用 alt 解决,或者把循环画成多个分支。

为什么不专业:组合片段类型本身就是“语义标签”。用错了标签,读者会按错误语义理解。

修正

  • alt:互斥(只走一条)
  • opt:可选(可能发生也可能不发生)
  • loop:重复发生(次数/条件写清)
  • par:并行发生(同时进行)

错误 9:把异常/错误处理完全省略,图只画“成功路径”

症状:图非常顺滑,全是 OK,但实际系统里 80% 的复杂度来自失败处理。

为什么不专业:评审/测试/运维关心的是:失败时怎么退、怎么补、怎么告警。只画成功路径的图,往往只适合“概念演示”,不适合“可交付设计”。

修正:至少补齐 2 类失败:

  • 参数/权限失败(早失败)
  • 下游依赖失败(超时、异常、返回错误码)

alt 分支表达错误返回。


错误 10:超时没有语义(只写“超时”两个字)

症状:图上标“超时”,但看不出谁超时、在哪等、超时后做什么。

为什么不专业:超时不是一个“事件名称”,它是“等待发生在谁身上 + 等待多久 + 超时后的策略”。

修正:明确 3 件事:

  1. 谁在等待(客户端/服务A/服务B)
  2. 等待对象(RPC 返回/回调/队列消费)
  3. 超时策略(返回错误/重试/降级/补偿/异步继续)

在图里可以用注释:note right of ServiceA: timeout=3s, retry=2


错误 11:重试画法不清:到底“谁在重试”?重试间隔是什么?

症状:写一句“失败重试”,但不画重试循环。

为什么不专业:重试会改变时序(重复调用、可能并发叠加),也会改变对下游的压力。

修正:用 loop 表达重试,并在循环条件里写清:

  • 最大次数:max=3
  • 退避:backoff=200ms,400ms,800ms
  • 触发条件:only on timeout/5xx

错误 12:把“幂等”当成一句口号,图里看不出幂等点

症状:文档写“接口幂等”,图里完全没有体现:幂等键在哪传、在哪校验、重复请求如何返回。

为什么不专业:幂等是可执行的机制,不是愿望。没有落点就无法测试,也无法保证线上不出事故。

修正:在图里明确幂等落点:

  • 客户端/上游:携带 Idempotency-Key / bizKey
  • 服务端:先查去重表/幂等表,再决定走“首次处理”还是“直接返回结果”

并用 alt 画出两条路径:[首次] vs [重复]


错误 13:回调(callback)被画成“返回消息”,导致误解

症状:支付回调、Webhook 回调被画成虚线返回,像是同步返回。

为什么不专业:回调是新的一次请求,通常发生在不同的时间点,调用方/被调用方角色也会反转。

修正:把回调画成新的消息序列:

  • 第一次:你调用第三方
  • 之后:第三方回调你(可能多次、可能延迟)

并补上“签名校验/重复回调/幂等处理”的分支。


错误 14:把消息写得像“数据库操作日志”(过度细节)

症状:每一步都画成 INSERT ...UPDATE ...SELECT ...,整张图密密麻麻。

为什么不专业:时序图的目标是表达交互协议与关键边界,不是替代代码。细节过载会掩盖重点。

修正:只在“决定语义”的地方画数据库:

  • 幂等去重(是否已处理)
  • 状态机更新(状态从何到何)
  • 关键写入(订单创建、支付记录、出账记录)

其余地方用更高层的消息名:CreateOrder()UpdateStatus()


错误 15:把“内部函数调用”当成“对象间消息”画

症状:同一个服务内部的方法调用也被画成对外消息,导致参与者爆炸。

为什么不专业:时序图通常用于跨边界沟通(系统/服务/模块之间)。内部实现细节画太多会让读者抓不住边界。

修正

  • 内部调用可以用“自调用(self-call)”简略表达
  • 或者直接省略,把内部细节放到伪代码/注释

错误 16:没有明确“系统边界”,读者不知道哪些是你们的责任

症状:第三方、外部系统、客户端都混在一起,没有标注边界。

为什么不专业:责任边界不清,会导致评审时争论“这一步谁保证”。

修正

  • 在标题或注释里写清:本图覆盖范围
  • 外部系统在参与者名上标注 <<external>>
  • 或用注释框标出“系统边界”

错误 17:并发没有画出来(用顺序假装并发)

症状:实际上是并行发生的两件事(比如写 DB + 发 MQ),图里被画成严格先后。

为什么不专业:并发关系会影响一致性、事务边界、失败处理与性能。顺序画错会误导实现。

修正:用 par 组合片段表达并行,或者用注释明确“可并行”。


错误 18:状态变化不明确:看不出“什么时候变成 SUCCESS/FAILED”

症状:图里有很多调用,但没有状态更新点。测试和运维会问:“我怎么判断它成功了?”

为什么不专业:很多线上问题本质是“状态机没设计清楚”。

修正:在关键节点画出状态更新(不必每次都画 DB 细节):

  • status=RUNNING
  • status=SUCCESS
  • status=FAILED(next_run_time=...)

并且在失败分支里说明是否重试、何时进入 DEAD。


错误 19:忽略“可观测性”:没有任何日志/指标/trace 的关键点

症状:图只画业务调用,不提 traceId、不提关键日志点。

为什么不专业:真实系统里,排障靠可观测性。时序图如果完全不体现,你的设计就很难落地成“可运维系统”。

修正:只要 3 个点就够:

  • 请求入口生成/透传 traceId
  • 关键外呼记录请求/响应摘要(脱敏)
  • 关键状态变更打点(成功/失败/重试次数)

note 标注即可,不用画成参与者。


错误 20:把图当“结束语”,不提供检查清单与用例路径

症状:图画完就结束,没有告诉读者“怎么验证这图是对的”。

为什么不专业:可交付的时序图应该能直接指导测试用例与评审。

修正:在图后附两样东西:

  1. 检查清单(见下)
  2. 用例路径枚举:成功路径 + 主要失败路径(参数失败、鉴权失败、下游超时、重复请求等)

一份可直接照抄的“时序图检查清单”(画完逐条对照)

  • 参与者命名是否清楚(职责明确、边界明确)
  • 同步/异步语义是否一致(团队统一口径)
  • 关键返回是否表达清楚(尤其是错误返回)
  • 是否补齐至少 2 条失败路径(早失败 + 下游失败)
  • 分支是否用 alt/opt 表达且 guard 条件清晰
  • 重试是否用 loop 表达且次数/退避清晰
  • 幂等是否有落点(幂等键、去重、重复请求返回)
  • 回调是否画成“新请求”而不是“返回消息”
  • 状态更新点是否明确(SUCCESS/FAILED/DEAD 等)
  • 并发是否表达清楚(该并行的不要硬画顺序)

FAQ:画时序图时大家最爱争的 5 个问题

1)返回消息到底要不要画?

不需要每条都画,但关键返回一定要画

  • 是否成功
  • 错误码/错误类型
  • 是否触发重试/补偿

2)时序图要画到多细?

以“评审能落实现、测试能落用例”为标准。通常:

  • 关键边界(跨服务/跨系统)要细
  • 内部实现细节可以粗

3)异常和超时要不要都画?

至少画你们最常见、最致命的那几类:超时、重试、幂等、回调重复、下游失败。否则图只能当教材,不能当设计。

4)同一张图里要不要把所有分支都画完?

不建议。复杂系统一般拆成多张图:

  • 主流程(Happy Path)
  • 失败处理/重试补偿
  • 回调与幂等

5)有没有更快的画图方式?

如果你经常需要把“文字交互”转成图,建议先用工具生成骨架,再按本文清单修正:

  • 在线编辑 + 实时预览
  • 直接导出 SVG/PNG/JPEG
  • 需要时再导出 draw.io 继续加工

入口在这(带文章来源标记,方便你后面统计): 手打时序图 - 时序图在线制作



加餐:3 个“最容易吵起来”的边界问题(给评审对齐用)

很多团队画时序图,争论点不在画法,而在边界。下面这 3 个加餐段落,建议你直接贴进文章末尾:它能显著减少评审时的“各说各话”。

1)要不要把数据库画成参与者?什么时候必须画?

不必每次都画 DB。时序图不是 SQL 日志;过度画 DB 会让图失焦。

但下面 4 种情况,建议必须画(或者至少用 note 写清楚):

  • 幂等:先查去重/幂等表,决定“首次处理”还是“直接返回”
  • 状态机:关键状态变更点(RUNNING→SUCCESS/FAILED/DEAD)
  • 事务边界:在哪开启/提交/回滚,是否涉及事务外消息
  • 一致性风险:比如写库成功但发 MQ 失败,要不要补偿

你可以用一句规则给团队对齐:

“DB 只画会改变外部可观察行为的读写;纯内部实现细节不画。”

2)要不要把缓存/队列也画出来?

建议按“对结果有无影响”来决定。

  • 缓存只是优化(miss 仍能查库)→ 可以省略,用一句注释说明“有缓存”
  • 缓存决定语义(比如幂等 token 存在与否、限流计数)→ 建议画出来
  • 队列是异步边界(会引入延迟、重试、顺序性问题)→ 强烈建议画出来

队列场景最容易遗漏的是:

  • 消费失败后的重试策略
  • 重复消费的幂等处理
  • DLQ(死信队列)/补偿任务

3)同一个时序图里要不要把“所有异常”都画全?

不建议在一张图里把所有异常都塞满,否则图会变成“噪声墙”。更好的做法是拆图:

  • 图 A:主流程(Happy Path)
  • 图 B:最关键的 2~3 条失败路径(鉴权失败、下游超时、幂等重复)
  • 图 C:回调/通知链路(如果有)

这样测试也更好落用例:每张图天然对应一组测试路径。


一个示例:把“支付回调重复”画成不歧义的 alt(可照抄)

如果你要在时序图里表达“回调可能重复,服务端要做幂等”,可以用下面这种结构(文字版,画图时对应成 alt 分支):

  • 第三方 → 你:POST /callback (bizKey, sign, ...)
  • 你:校验签名
  • alt
    • [bizKey 未处理]:落库/执行业务 → 返回 success
    • [bizKey 已处理]:直接返回 success(不要再执行业务)

关键点是:

  • 重复回调不是错误,返回 success 才能让对方停止重试
  • 幂等分支要画出来,否则读者会默认“重复回调会重复执行业务”

写给想更进一步的人:一张时序图如何支撑测试用例设计

很多测试同学看时序图,最关心的是“我怎么从图里拆出用例”。你在文章里加上这段,会让它更像一篇“能落地的教程”。

一个简单方法:

  1. 先把图里的 alt/opt/loop 当成“用例分叉点”
  2. 每个分支至少产出 1 条用例
  3. 对有重试的路径,再补 2 条:
    • 重试后成功
    • 重试耗尽进入失败(DEAD/告警)

例如一条典型接口链路,至少应该覆盖:

  • 正常成功
  • 鉴权失败
  • 参数校验失败
  • 下游超时(触发重试)
  • 下游连续失败(进入失败终态)
  • 幂等重复请求(应返回同样结果,不重复副作用)

你会发现:当你能从时序图自然拆出这些路径时,说明这张图的“语义”大概率是清楚的。