组合片段 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:分支互斥要表达清楚(别让读者猜“同时满足怎么办”)
如果多个条件可能同时成立,你要明确:
- 这些条件是不是互斥?
- 如果不互斥,优先级是什么?
实践上有两种稳定写法:
- 把条件写成互斥
[A]/[!A][status==PAID]/[status!=PAID]
- 显式写 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 在你的在线编辑器里怎么快速画(降低成本)
如果你用的是「时序图生成器」这类工具,实际操作通常是:
- 左侧编辑器先点选 生命线(参与者),把 Client / API / Auth / OrderService 建起来
- 再点选 消息,按“请求→鉴权→业务→响应”的顺序把主干拉出来
- 最后点选 组合片段,选择
alt,用鼠标框住“鉴权分支”涉及的那一段消息 - 在
alt的每个分支顶部输入 guard,比如[token valid]/[else] - 右侧实时预览一眼看布局是否可读(对齐、间距、是否遮挡)
做完之后,通常还需要输出到文档/评审材料里:
- 导出 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 statusDB --> OrderService: statusalt [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 你真的想表达的分支有哪些
登录常见分岔点:
- 基础校验:参数是否齐全、验证码格式是否正确
- 风控判断:是否需要额外验证(滑块、人机、短信)
- 验证码校验:通过/失败/过期
- 账号密码校验:通过/失败
- 登录态生成: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、熔断、降级……
为什么不行:
- 正常读图的人想先看“主流程”,但你把主流程切成碎片
- 测试用例也会被迫按“异常类型”拆,而不是按“决策点/路径”拆
更好的修正方式(两步):
- 先用一个
alt把“业务互斥路径”拆清楚
- 例:
[库存足够]/[else]
- 对“技术异常”(超时/5xx/熔断)用更清晰的表达方式:
- 在关键调用旁边加注释:
note right: timeout -> retry(3) -> fallback - 或把异常处理抽成单独的时序图(例如“库存服务调用的超时与降级”)
这样你得到的是两张各自可读的图,而不是一张“所有可能性的大杂烩”。
12. 最小可落地模板:把 alt 直接写成团队规范(可复制到文档)
如果你负责团队规范/文档,可以直接把下面这一段贴到《时序图绘制规范》中。它比“应该写清楚”这种空话更可执行。
alt 组合片段规范(建议版):
alt仅用于表达互斥分支(if/else-if/else)。可选行为用opt,循环用loop,并行用par。- 每个分支必须有 guard 条件,guard 必须可判定(变量/返回码/状态值/事件),禁止仅写“成功/失败/异常”。
- 建议每个
alt都包含[else]兜底分支,并明确兜底分支对调用方的返回(码/错误对象/提示)。 - 分支内消息粒度应大致对称;失败分支至少包含:返回 + 关键日志/审计(如需要)+ 清理/回滚(如需要)。
- 单个
alt分支数建议不超过 3;超过则拆决策点或拆图。
把它作为评审 checklist 的一部分,能显著减少“读不懂/各说各话”的问题。