时序图栏目 ·

组合片段 alt 怎么用:分支(if-else)在时序图里怎么画才不歧义

alt 组合片段用于在 UML 时序图中表达 if-else 分支。本文从研发/测试/架构视角讲清 alt 的语义、最小规范、常见反例与修正、检查清单与 FAQ,并给出可直接照抄的分支画法。

如果你正在画时序图(UML Sequence Diagram)并且需要表达 if-else 分支,最稳妥的画法几乎总是:

  • 组合片段(Combined Fragment)里的 alt
  • 给每个分支写清楚 guard 条件(通常写成 [...]
  • 尽量补一个 else 分支(哪怕只是“返回错误/拒绝请求”)

这样做的价值很实际:读图的人能一眼知道“分支的判断依据是什么、每条分支到底会发生哪些消息交互”。否则你画出来的分支,很容易变成“看起来像分支,其实全靠读者脑补”。

下面这篇会用产品/研发/测试/架构的视角把 alt 的用法讲透:最小规范、常见误解、反例修正、检查清单和 FAQ。你看完就能把“分支画不清”这类争论基本消掉。


1. alt 是什么:一句话定义(别背概念,抓住语义)

alt(Alternative)是时序图里的 组合片段类型,用来表达:

在同一段时间区间内,根据条件不同,只会发生其中一个分支的交互序列(if / else-if / else)。

也就是说,alt 不是“把所有可能都列出来”;它表达的是:这些“互斥”的交互方案里,运行时只走一条

你可以把它当成代码里的:

if (cond1) {
  ...messages A...
} else if (cond2) {
  ...messages B...
} else {
  ...messages C...
}

但注意:时序图表达的是 对象之间消息交互的顺序,不是单纯业务规则本身。alt 的每个分支,应该都能展开成“谁调用谁、什么时候返回、失败怎么返回”等交互。


2. 什么时候该用 alt(以及什么时候你其实该用别的)

2.1 适合用 alt 的典型场景(研发/测试最常用)

下面这些场景,只要你在图里想表达“分支”,基本都用 alt

  • 鉴权成功/失败:token 有效 vs 无效
  • 参数校验通过/不通过:缺字段、格式错误、越权
  • 资源存在/不存在:订单存在 vs 不存在
  • 状态不同导致不同流程:订单已支付 vs 未支付 vs 已关闭
  • 外部依赖成功/失败:调用风控/短信/支付网关返回不同码
  • 幂等分支:已处理过 vs 首次处理

这些分支的共性是:

  • 分支之间互斥
  • 每个分支内部都是“可交付”的交互序列
  • 测试需要据此落用例(能直接拆成用例路径)

2.2 你可能不该用 alt 的情况

  • 只是可选的一段交互(没有互斥) → 多数时候用 opt
    • 例:如果用户设置了昵称,额外调用一次“敏感词检测”
  • 重复执行的交互 → 用 loop,不要用 alt 把“循环次数”画成多个分支
  • 并发发生的交互 → 用 par,不要用 alt 假装“并发是分支”
  • 你要表达的是状态机切换(状态→事件→状态)→ 用状态图更贴切

一句话判断:

  • “只可能发生一个分支”→ alt
  • “可能发生,也可能不发生”→ opt
  • “发生很多次”→ loop
  • “同时发生”→ par

3. alt 的最小规范(画对不难,但要“写得不歧义”)

如果你只记 5 条规则,就记这 5 条:

规则 1:每个分支都要写 guard 条件(写到能落测试用例)

alt 的每个 operand(分支块)左上角写:

  • [condition]

条件要可判定,而不是形容词。

  • 好:[token valid][order.status == PAID][riskResult == REJECT]
  • 差:[成功][失败][异常][风控不过](“不过”依据是什么?)

测试/联调时最怕的是:你画了“成功/失败”,但没有定义“什么叫成功/失败”,最后每个人脑子里都有一套“成功”。

规则 2:分支互斥要表达清楚(别让读者猜“同时满足怎么办”)

如果多个条件可能同时成立,你要明确:

  • 这些条件是不是互斥?
  • 如果不互斥,优先级是什么?

实践上有两种稳定写法:

  1. 把条件写成互斥
  • [A] / [!A]
  • [status==PAID] / [status!=PAID]
  1. 显式写 else-if 的排他
  • [cond1]
  • [!cond1 && cond2]
  • [else]

规则 3:建议补一个 [else](哪怕你认为“不可能发生”)

只画 if 不画 else,在评审时经常会被追问:

  • “那条件不成立时系统怎么办?”
  • “客户端等多久?超时?返回什么码?”

你完全可以让 else 分支很短:返回错误码、记录日志、拒绝请求。

补 else 的本质是:补齐系统边界行为

规则 4:分支内的消息顺序要自洽(别在 A 分支里写‘调用’,在 B 分支里只写‘结果’)

分支要能互相对比。最常见的坑是:

  • A 分支写得很细(调用-返回-落库-发消息)
  • B 分支写得很粗(“失败”两个字)

这会导致读者误解:失败时到底发生了什么?有没有落库?有没有风控记录?有没有埋点?

规则 5:alt 的框应该覆盖“分支相关的所有消息”,不要只框一半

如果你把“判断条件”发生之后的一些共同消息画在 alt 外面,要小心:

  • 这些“共同消息”在所有分支都一定发生吗?
  • 还是只有部分分支发生?

一旦你把“只在成功分支发生的动作”画到 alt 外面,就会产生非常隐蔽的歧义。


4. 一个能直接照抄的 alt 画法(鉴权示例)

下面用“接口鉴权”举例。参与者:

  • Client(调用方)
  • API(网关/服务入口)
  • Auth(鉴权服务)
  • OrderService(业务服务)

4.1 文字交互(很多团队的输入长这样)

  • 客户端请求创建订单
  • API 校验 token
  • token 有效:调用 OrderService 创建订单并返回
  • token 无效:直接返回 401

4.2 转成时序图的要点

alt 的两个分支分别写 guard:

  • [token valid]
  • [else][token invalid]

每个分支里把消息补齐:

  • “校验 token”是谁发起的?调用哪个服务?返回什么?
  • 失败分支返回什么?是否记录审计?

4.3 在你的在线编辑器里怎么快速画(降低成本)

如果你用的是「时序图生成器」这类工具,实际操作通常是:

  1. 左侧编辑器先点选 生命线(参与者),把 Client / API / Auth / OrderService 建起来
  2. 再点选 消息,按“请求→鉴权→业务→响应”的顺序把主干拉出来
  3. 最后点选 组合片段,选择 alt,用鼠标框住“鉴权分支”涉及的那一段消息
  4. alt 的每个分支顶部输入 guard,比如 [token valid] / [else]
  5. 右侧实时预览一眼看布局是否可读(对齐、间距、是否遮挡)

做完之后,通常还需要输出到文档/评审材料里:

  • 导出 SVG(适合文档与高清缩放)
  • 导出 PNG/JPEG(适合飞书/邮件/需求评审贴图)
  • 如果要进 draw.io 继续二次编辑,导出 draw.io 格式更省事

如果你希望更快一点,也可以直接用 AI 生成:把“文字交互 + 参与者 + 分支条件”贴进去,让 AI 先出一个可用底稿,你再手工调整 guard、消息命名和布局。

你可以用这个链接直接打开并开始画:

(这不是硬广——核心是把“画 alt 的步骤”变得可复用,少浪费你在排版对齐上的时间。)


5. 常见反例:为什么你的 alt “看起来对,但读起来很累”

这一节专门写给做过评审的人:很多时序图的问题,不是语法错,而是 信息结构不对

反例 1:guard 写成“成功/失败”

问题画法:

  • [成功] / [失败]

为什么不行:

  • 成功的判定口径不明确
  • “失败”可能有十几种:鉴权失败、限流、参数错、下游超时……

修正方式:

  • 把 guard 写成可判定条件或返回码:
    • [authResult == OK]
    • [authResult == INVALID_TOKEN]
    • [authResult == EXPIRED]
  • 或者按“调用链路的关键分岔”来写:
    • [Auth.verify(token) returns OK]
    • [Auth.verify(token) returns ERROR]

反例 2:分支内细节不对称

问题画法:

  • 成功分支:写了 6 条消息
  • 失败分支:只写“返回失败”

为什么不行:

  • 测试没法拆用例:失败时有没有调用 Auth?有没有落审计?有没有埋点?
  • 研发没法对齐实现:失败路径常常才是 bug 高发区

修正方式:

  • 至少补齐:
    • 返回码/错误对象
    • 是否记录审计/日志
    • 是否触发告警/埋点
    • 是否需要清理上下文(比如释放锁、回滚)

反例 3:把“业务状态”当成 guard,但不写状态来源

问题画法:

  • [订单已支付] / [订单未支付]

为什么不行:

  • 读者会问:订单状态是谁查出来的?查 DB 还是查缓存?是否可能脏读?

修正方式:

  • 在进入 alt 前补一条“查状态”的消息,或者把 guard 写成“查状态结果”:
    • OrderService -> DB: SELECT status
    • DB --> OrderService: status
    • alt [status == PAID] ... [else] ...

反例 4:alt 框只框住“返回”,把关键动作放外面

问题画法:

  • alt 内只画了“返回成功/返回失败”
  • 但“创建订单/写库/发消息”画在 alt 外面

为什么不行:

  • 它暗示:无论成功还是失败都会“创建订单/写库/发消息”
  • 或者让读者完全不知道哪些动作属于哪个分支

修正方式:

  • 把分支相关的动作都放进对应分支
  • 只有“100% 两边都会发生”的动作才能放在 alt 外面(而且建议你自问三遍:真的 100% 吗?)

反例 5:一个 alt 里塞 5~8 个分支

问题画法:

  • [A] [B] [C] [D] [E]

为什么不行:

  • 时序图的主要价值是“读起来像一段可执行的故事”
  • 分支太多,故事断成碎片,读者只会跳过

修正方式:

  • 把分支收敛到 2~3 条:
    • 常见:成功 / 失败
    • 或:成功 / 可重试失败 / 不可重试失败
  • 其余细分放到:
    • 错误码规范文档
    • 失败处理专项时序图
    • 或在失败分支里用注释列举错误码

6. 场景拆解:登录/验证码/风控的分支怎么画不乱

很多团队画“登录”时序图会画崩,原因不是登录复杂,而是把所有分支都堆在同一层。

这里给一个更可读的拆解方式:分层使用 alt + opt

6.1 你真的想表达的分支有哪些

登录常见分岔点:

  1. 基础校验:参数是否齐全、验证码格式是否正确
  2. 风控判断:是否需要额外验证(滑块、人机、短信)
  3. 验证码校验:通过/失败/过期
  4. 账号密码校验:通过/失败
  5. 登录态生成:token 生成成功/失败(极少但要有兜底)

如果你把 1~5 全塞进一个 alt,会变成“分支爆炸”。

6.2 更稳的画法:把分支按“决策点”分开

  • 决策点 1(是否需要风控增强)用 alt

    • [riskRequired] → 走增强验证
    • [else] → 跳过
  • 增强验证内部再用一个 alt

    • [captcha ok] / [else]
  • 账号密码校验内部也用 alt

    • [password ok] / [else]

这种画法的好处:

  • 每个 alt 分支都很短,可读性高
  • 测试能直接按“决策点”拆用例
  • 研发实现也更贴近代码结构(多个 if,而不是一个巨型 if)

如果你在画图工具里操作,一般就是:

  • 左侧点选 alt,先框住“风控是否需要”的那段消息
  • 再在成功分支里,继续点选 alt,框住“验证码校验”的消息
  • 右侧实时预览检查嵌套是否挤在一起,必要时拉开间距、调整生命线位置

当你需要交付给产品/测试/外部团队时,可读性比“画得全”更重要


7. 让 alt 更“可交付”的细节:命名、返回、错误码、日志

做过联调的人都懂:分支图最容易在“边界行为”上翻车。

这里给你一套实用标准(不是学院派):

7.1 guard 条件尽量写“可观测信号”

可观测信号包括:

  • 关键变量:status == PAID
  • 返回码:code == 0
  • 外部系统响应:RiskAPI returns REJECT

不要写成纯业务口号:

  • [风控通过](通过的阈值?策略?版本?)

7.2 失败分支必须写清“对谁返回什么”

至少写出:

  • 返回对象/错误码:401 / 403 / 429 / 5xx
  • 是否立即返回还是需要等待下游(会影响超时/重试策略)

7.3 “记录日志/埋点/审计”要不要画?

结论:当它影响排障与合规,建议画;当它只是实现细节,不必硬塞

你可以用两种方式表达:

  • 作为独立生命线(Log/Audit)画一条异步消息
  • 或者写在注释里:note right of API: record audit(logType=LOGIN_FAIL)

把它画出来的好处是:测试/安全/运维不会在评审会上临时加需求。


8. 检查清单:画完 alt 之后,5 分钟自检能省一周扯皮

把这份清单当成“交付前自测”。每项都能回答“是/否”。

8.1 语义与结构

  • 这个片段真的满足“互斥选择其一”吗?如果不是,是不是该用 opt/loop/par?
  • 每个分支都有 guard 条件(不是“成功/失败”这种空条件)
  • 条件之间互斥关系明确(或优先级明确)
  • [else] 或等价兜底(至少描述系统边界行为)

8.2 交互完整性

  • 每个分支内部都能形成完整交互(请求→处理→响应)
  • 成功/失败分支的“粒度”大致对称(别一个写 10 条,一个写 1 条)
  • 返回消息(或返回结果)在分支里写清楚了

8.3 可实现与可测试

  • guard 条件能映射到具体实现(变量、返回码、事件)
  • 测试能据此拆成用例路径(至少成功/失败/边界)
  • 错误码/异常类型与现有规范一致(或在图旁注明新增)

8.4 可读性

  • 一个 alt 分支数量 <= 3(超过就考虑拆图/拆决策点)
  • 关键信息写在 guard/消息命名里,而不是长段注释里
  • 右侧预览下不会出现消息线交叉、文本遮挡(交付前务必检查)

如果你想把这套清单固化到团队流程里,最省事的方式是:把文字交互贴进工具里生成底稿,然后按清单逐项补齐 guard、返回、兜底分支。

再次给你入口(带上文章 id 便于归因统计):


9. FAQ:alt 相关的高频争论(一次说清)

Q1:alt 和 opt 到底怎么选?

  • alt:多条路径 互斥,运行时只会走一条(if-else)
  • opt:这段交互 可能发生,也可能不发生(if without else)

如果你只画 opt,但实际上存在失败返回,那你是在“把失败藏起来”。对联调和测试非常不友好。

Q2:alt 能嵌套吗?会不会太复杂?

能嵌套,而且很多真实系统必须嵌套。

建议原则:

  • 嵌套可以有,但每层 alt 都应该短
  • 嵌套超过 2~3 层就考虑拆图:
    • 把某个子流程抽成单独时序图(例如“验证码校验子流程”)

Q3:guard 条件应该写业务条件还是技术条件?

优先写 读者能验证的条件

  • 面向研发/测试:技术条件(返回码、状态值、事件)更落地
  • 面向产品:业务条件可以保留,但建议在旁边补技术口径

你可以混合写:

  • [风控要求增强验证(riskRequired=true)]

Q4:分支里要不要画返回消息(虚线箭头)?

如果返回的信息会影响后续分支判断、错误处理或用户体验,建议画。

例如:

  • 鉴权失败返回 401
  • 风控拒绝返回具体 reason

如果只是“同步调用必然返回”的普通返回,不画也行,但要保证读者不会因此误解。

Q5:一个 alt 里能不能写“异常”分支?

可以,但建议把“异常”拆成可操作的类型:

  • [timeout]
  • [rate limited]
  • [downstream 5xx]

这样测试能写用例,研发能写重试/降级,架构能讨论 SLO。


11. 再补两组“反例 + 修正”:评审时最容易踩的坑

为了让全文达到可交付的深度,这里再补两组实际评审中很常见的坑。你可以把它们当成“看到就立刻改”的模式库。

反例 6:guard 写得过于抽象,导致分支口径漂移

问题画法:

  • [用户有权限] / [用户无权限]

为什么会出事:

  • “权限”在不同模块口径不同:角色权限、资源权限、数据权限、AB 实验开关都可能被叫做“权限”
  • 一旦联调,就会出现“我以为你说的是 A 权限,你以为我说的是 B 权限”的典型扯皮

修正方式:

  • 把 guard 写成“判定来源 + 判定结果”,让口径落到具体接口/字段:
    • [AuthZ.check(userId, resourceId, action) == ALLOW]
    • [else](并在消息里写清返回 403 还是 404,避免信息泄露问题)

如果你担心 guard 太长,可以把详细条件写在注释里,但 guard 本身至少要可定位:

  • [authz == ALLOW](注释:authz = AuthZ.check(…))

反例 7:把“异常处理”混进 alt,结果正常路径被打断

问题画法:

  • 一个 alt 同时包含:成功、业务失败、下游超时、下游 5xx、熔断、降级……

为什么不行:

  • 正常读图的人想先看“主流程”,但你把主流程切成碎片
  • 测试用例也会被迫按“异常类型”拆,而不是按“决策点/路径”拆

更好的修正方式(两步):

  1. 先用一个 alt 把“业务互斥路径”拆清楚
  • 例:[库存足够] / [else]
  1. 对“技术异常”(超时/5xx/熔断)用更清晰的表达方式:
  • 在关键调用旁边加注释:note right: timeout -> retry(3) -> fallback
  • 或把异常处理抽成单独的时序图(例如“库存服务调用的超时与降级”)

这样你得到的是两张各自可读的图,而不是一张“所有可能性的大杂烩”。


12. 最小可落地模板:把 alt 直接写成团队规范(可复制到文档)

如果你负责团队规范/文档,可以直接把下面这一段贴到《时序图绘制规范》中。它比“应该写清楚”这种空话更可执行。

alt 组合片段规范(建议版):

  1. alt 仅用于表达互斥分支(if/else-if/else)。可选行为用 opt,循环用 loop,并行用 par
  2. 每个分支必须有 guard 条件,guard 必须可判定(变量/返回码/状态值/事件),禁止仅写“成功/失败/异常”。
  3. 建议每个 alt 都包含 [else] 兜底分支,并明确兜底分支对调用方的返回(码/错误对象/提示)。
  4. 分支内消息粒度应大致对称;失败分支至少包含:返回 + 关键日志/审计(如需要)+ 清理/回滚(如需要)。
  5. 单个 alt 分支数建议不超过 3;超过则拆决策点或拆图。

把它作为评审 checklist 的一部分,能显著减少“读不懂/各说各话”的问题。