半年后有人提出把订单状态回写从 outbox 改成事务内双写,理由是"少一层延迟"。会议室里没人能说出当初为什么不用事务内双写,只知道代码里有个 outbox 表。讨论从零开始,两个小时后又回到了原点——中间还烧掉了一次跨团队评审。
**决策记录的价值不在存档,而在让"已经讨论过的事"不必再讨论一遍。**它的成本也很明确:写一份约半小时,维护它约等于零,但它要求在下笔时就想到"什么情况下这个决定该被推翻"。这一条是绝大多数记录缺失的部分,也是它退化成历史遗迹的主因。
1. 决策记录要解决的三个问题
| 问题 | 回答不了会怎样 | 记录里对应哪一段 |
|---|---|---|
| 当时看到了什么 | 后人以为当时信息更全,于是重做一遍调研 | 上下文 |
| 为什么这样选 | 只能在若干方案间再来一轮辩论 | 决策 + 代价 |
| 什么情况下要重开 | 决定永久有效,或无人敢改 | 重开条件 |
第三行是最常缺的,也是唯一能让记录"过期"的机制。没有它,记录会一直有效到某次事故把它撞碎;有它,记录可以在条件变化时被主动替换。
2. 一页纸的四段结构
─── 四段结构(占位,一页以内)
上下文: 支付回调需在同一逻辑里更新订单状态并发通知;两条路径都在本地事务内,
消息失败会回滚业务,触发第三方重投与人工核对。量级:按 X 版本核对。
决策: 状态变更写 outbox,独立投递器发送,成功再标记。
代价: 通知延迟由毫秒级变为最多 1s(按 X 版本核对);新增 outbox 表与清理任务;
投递器故障时通知会积压,接受该风险。
重开条件: 通知延迟被要求进入 200ms 以内;或投递器积压超过 N 条且清理任务无法自愈。
四段里只有"重开条件"要求写具体数字或具体信号。写成"如果架构变化"等于没写——重开条件必须是可判定的,最好能对应到一条指标或一个需求变更。
代价那一段也常被写成"稍有延迟"。它必须落成量级:从多少到多少、按哪个版本核对。没有量级的代价,半年后无法判断还能不能接受。
3. 该记什么的判据
不要用"重要的事都记",那个判据无法执行。用两个变量:
| 判据 | 高 | 低 |
|---|---|---|
| 决策成本 | 需要调研、多方对齐、多次评审 | 一次讨论即可定 |
| 信息丢失速度 | 参与者会离开、上下文只存在于对话里 | 结论已固化在代码与测试里 |
─── 记录取舍(占位)
高成本 + 快丢失 → 必须记(选型、契约、数据模型、跨团队边界)
高成本 + 慢丢失 → 记(结论已进代码,但理由仍需可查)
低成本 + 快丢失 → 记一行(在 PR 描述或变更单里,不进 ADR)
低成本 + 慢丢失 → 不记(命名、目录结构、库的小版本选择)
两个变量都在低位的组合最容易被过度记录:把"我们决定用 4 空格缩进"写成 ADR,是记录体系崩坏的起点——不是因为这条错,而是因为它让读者认为 ADR = 决策日志。
4. 不该记什么
| 内容 | 为什么不该进 ADR | 该放哪 |
|---|---|---|
| 可回滚的低成本选择 | 半年后无检索价值,只增加噪声 | PR 描述、提交信息 |
| 纯执行细节 | 改代码就会失效,记录变成负债 | 代码注释、README |
| 会议纪要 | 讨论过程不是承诺 | 会议记录或工单评论 |
| 未决策的选项清单 | 会与最终决策混淆 | 调研文档,链接进 ADR |
分界方法:ADR 记的是承诺,不是过程。承诺一旦写下就约束后续行为;过程只需要被追溯,不需要被维护。
5. 粒度与位置
| 位置 | 被读到的概率 | 主要问题 |
|---|---|---|
代码库 docs/adr/ |
高:改这块代码的人会看到 | 与代码同仓,需要评审纪律 |
| 文档库 / 知识库 | 低:只有主动搜索才会遇到 | 与代码脱节,链接先烂 |
| 工单系统 | 中:与具体变更绑定 | 难以横向浏览与检索 |
| 聊天记录 | 接近零 | 无法被结构化引用 |
放在代码库的收益很直接:改这块代码的人,在动手前就会被目录里的记录拦一次。放在文档库的记录,只有已经知道它存在的人才会去读——这恰好排除了最需要它的人。
粒度建议一事一文件,文件名带编号与短标题,便于在提交信息里引用:
docs/adr/
├── 0001-record-architecture-decisions.md
├── 0014-order-write-path-uses-outbox.md
├── 0017-order-query-uses-read-replica.md
└── 0021-replace-0017-cache-first-read-path.md ← supersede 前一条
6. "决策"与"方案"的区别
| 维度 | 决策 | 方案 |
|---|---|---|
| 形态 | 取舍的承诺:用 A 换 B | 实施路径:怎么把 A 做出来 |
| 变更成本 | 高,需要重开条件触发 | 低,随时可调整 |
| 失效方式 | 条件变化即失效 | 实施完成即完成 |
| 记录位置 | ADR | 设计文档、PR、任务拆解 |
把方案写进 ADR 的后果是:方案调整时记录没跟着改,读者看到的是"当时的实现思路",误以为是"必须遵守的约定"。判据:如果这段话在实现方式改变后仍然成立,它是决策;否则是方案。
7. 撤销与替代
─── supersede 关系(占位):不改写历史
adr: 0021
title: "订单查询改走缓存优先的读路径"
status: accepted
supersedes: [0017] # 0017 的读副本方案被替换
superseded_by: []
reopen_if: # 与本 ADR 的重开条件同源
- "缓存不一致投诉超过阈值"
- "读副本延迟降至可接受范围"
纪律有两条:旧记录不改正文,只改状态字段;新记录必须显式引用被替代的那条。改写历史会让引用它的提交信息与讨论全部失真;不写 supersede 链,则会出现两份互相矛盾的有效记录,读者只能靠猜。
─── 状态机(占位)
proposed → accepted → superseded
↘ deprecated(不再适用,但无替代)
8. 读者假设与评审的关系
写 ADR 时不要假设读者是评审者。评审者需要的是"这个方案对不对",而真实读者是半年后的自己或新人,他需要的是"当时有什么约束、为什么否掉另一个"。
这个假设差异会改变写法:给评审者写,会强调结论与优点;给半年后的自己写,会强调约束、代价、以及"哪些情况下这个结论不再成立"。
评审与记录的分工也要说清:评审产生决策,记录固定决策。评审会开完不落记录,等于决策只存在于在场者的记忆里;记录里没有评审痕迹(谁参与、否掉了什么),则无法判断这个决策的权威性。
─── 评审到记录的落点(占位)
评审结论: 采用 B 方案(读副本 + 缓存),否决 A(分库)与"不做"
→ 记录必填: 约束(P99 目标)、代价(读到滞后数据,窗口按秒级估计)
→ 记录必填: 否决理由(A 改造成本按季度计,当前收益不匹配)
→ 记录必填: 重开条件(P99 目标进入 200ms 以内,或滞后投诉超阈值)
9. 腐化信号与度量
| 腐化信号 | 说明 |
|---|---|
| 只有新增,没有 supersede | 决策从未被推翻,通常意味着没人敢重开 |
| 内容越来越长 | 从一页涨到五页,说明它变成了设计文档 |
| 无人引用 | 提交信息、评审材料里从不出现 ADR 编号 |
| 状态字段长期不更新 | 已废弃的决策仍在指引新代码 |
| 指标 | 口径 | 用来决定什么 |
|---|---|---|
| 有重开条件的比例 | 含可判定重开条件的 ADR 占比 | 记录是否具备过期机制 |
| 被引用次数 | 提交信息/评审材料中的引用 | 记录是否进入日常工作流 |
| 决策回溯耗时 | 从提问到找到依据的时间 | 记录是否真的省了讨论 |
第三项是最能说明问题的:如果回溯一次决策仍需要问三个人,记录体系就没在工作。
10. 排错:记录体系的五种失效
| 现象 | 真实成因 | 处理方向 |
|---|---|---|
| 记录写了没人看 | 放在文档库,改代码的人不会路过 | 迁到代码库同仓 |
| 半年后仍要重开讨论 | 记录里没有重开条件与代价量级 | 补齐两段,旧记录可增量补 |
| 两份记录互相矛盾 | 没有 supersede 链 | 补状态字段与引用关系 |
| 记录变成设计文档 | 混淆了决策与方案 | 方案移出,只留取舍 |
| 新人不敢改既有结论 | 记录未写"什么情况下失效" | 补重开条件,明确可被替代 |
11. 边界:什么时候正式 ADR 是过度设计
| 场景 | 为什么不该上 | 替代做法 |
|---|---|---|
| 团队极小(二三人) | 决策者与读者是同一人,信息未丢失 | PR 描述里写三行理由 |
| 决策影响半径小 | 一处代码内的选择,改回来很便宜 | 提交信息 + 代码注释 |
| 已由外部标准强制规定 | 记录无法改变结论,只是抄写 | 引用标准编号即可 |
| 一次性原型 / 短生命周期项目 | 记录成本无法摊销 | 保留 README 里的取舍一段 |
判断口径:**当"半年后重新讨论一次"的成本低于"维护记录体系"的成本时,就不该有正式 ADR。**记录不是越多越好,它只在信息会丢失、且丢失后重建昂贵时才划算。
12. 自测
- 决策记录的三段内容里,哪一段决定了它能否"过期"?
- "决策成本 × 信息丢失速度"这两个变量都在低位时,该不该记?
- 为什么 ADR 放在代码库比放在文档库更容易被读到?
- 判断一段话是决策还是方案,用哪条判据?
- 旧决策被推翻时,为什么不能直接改旧记录正文?
- 什么规模下,正式 ADR 的维护成本高于它省下的讨论成本?