2998 字
约 9 分钟
18
S16-A11 · 决策记录:ADR 该写什么、写到什么时候停
2024-12-31
2026-11-09

半年后有人提出把订单状态回写从 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. 自测

  1. 决策记录的三段内容里,哪一段决定了它能否"过期"?
  2. "决策成本 × 信息丢失速度"这两个变量都在低位时,该不该记?
  3. 为什么 ADR 放在代码库比放在文档库更容易被读到?
  4. 判断一段话是决策还是方案,用哪条判据?
  5. 旧决策被推翻时,为什么不能直接改旧记录正文?
  6. 什么规模下,正式 ADR 的维护成本高于它省下的讨论成本?
S16-A11 · 决策记录:ADR 该写什么、写到什么时候停
https://www.clxhxhhr.top/posts/6263/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。