决策记录ADR:让技术决策可追溯可演进
TL;DR:架构决策记录(ADR)是一份轻量文档,记录“做了什么决策、为什么、基于什么假设、放弃了什么”。它不是设计文档,而是决策的留痕——让半年后没人记得“为什么选了 A”时仍能回溯推理。ADR 的关键属性是可演进:新决策可取代旧决策,形成决策树而非孤立文件。一份合格的 ADR 必须包含上下文、决策、后果与假设,且假设是可证伪的——若假设不成立,决策应被推翻。
概念解释
ADR 的由来与定义
架构决策记录(Architecture Decision Record,ADR)由 Michael Nygard 在 2011 年的文章《Documenting Architecture Decisions》中提出。它的诞生源于一个普遍痛点:团队做过无数技术决策,但绝大多数只存在于 Slack 消息、会议纪要或某个人的脑子里,半年后无人能回答“为什么当时选了 A 而不是 B”。ADR 的定义很简单——每个重要决策写一份短文档(通常 1-2 页),放在代码仓库里随代码版本管理。
ADR 不同于设计文档:设计文档描述系统“长什么样”,ADR 描述“为什么决定让它长这样”。它也不替代 RFC——RFC 是决策前的讨论,ADR 是决策后的留痕。一份 ADR 回答四个问题:上下文(面临什么问题)、决策(选了什么)、后果(带来什么)、假设(基于什么前提)。
supersede(取代)机制
ADR 不是写完就冻结的。Nygard 提出 ADR 可被取代:当新决策推翻旧决策时,不删除旧 ADR,而是新建一份 ADR 并标注它取代了哪份旧 ADR,同时在旧 ADR 顶部标注“已被 ADR-XXX 取代”。这形成一棵决策树——能追溯每次决策变迁的来龙去脉。这是 ADR 区别于普通文档的核心属性:它让决策历史可演进且不丢失。
决策状态
每份 ADR 有生命周期状态:Proposed(提议中,待评审)→ Accepted(已采纳,将执行)→ Deprecated(已废弃)或 Superseded(已被取代)。状态让团队知道这个决策是否仍有效,避免基于过时 ADR 行动。
ADR 与 ADR-Tools / MADR
社区发展出多种 ADR 变体:轻量版(Nygard 原版四要素)、MADR(Multiple-Attribute Decision Record,含多属性评分)、Y-Statements(Why/What/How 结构)。本专栏用 Nygard 原版为主,因为它最轻量、上手最快,复杂决策可参考 MADR 的评分思路(见 决策矩阵与权衡:在多目标下显式取舍)。
为什么 ADR 要放代码仓库
ADR 放在代码仓库而非 Wiki/Confluence,是因为:① 随 git 历史可追溯每次修改;② 代码 review 流程天然覆盖决策评审;③ 与代码同生命周期,不会因 Wiki 迁移而丢失。这是 ADR 区别于“记在文档系统”的关键实践。
目录
| 章节 | 说明 |
|---|---|
| 为什么需要 ADR | 口头共识的问题 |
| ADR 的内容结构 | 上下文、决策、后果、假设 |
| supersede 与决策树 | 决策如何演进 |
| 软件工程示例 | 技术选型 ADR |
| 适用边界与误用 | 何时该写何时不该写 |
为什么需要 ADR
没有 ADR 的团队会反复经历同一类对话:
| 场景 | 没有 ADR | 有 ADR |
|---|---|---|
| “为什么用 MySQL 不用 PG?” | 没人记得,或各执一词 | 查 ADR-007,看当时上下文与权衡 |
| 出了问题,是该推翻决策还是执行偏差? | 重新开会从头争论 | 对比 ADR 的假设,看哪个前提变了 |
| 新人入职问“为什么这样设计” | 老人凭记忆讲,常遗漏 | 自己读 ADR 理解决策脉络 |
| 半年后想换技术栈 | 不确定当时选型的约束是否还在 | 翻 ADR 的假设清单,逐条核对是否仍成立 |
ADR 解决的核心问题是决策的遗忘与不可审阅性。它不保证选对,但保证选错后能快速定位“哪个假设错了”——这是从“反复争论”到“定向复盘”的关键跳跃。
ADR 的内容结构
一份合格的 ADR 包含四要素:
| 要素 | 回答什么 | 常见错误 |
|---|---|---|
| 上下文(Context) | 面临什么问题、有什么约束 | 只写结论不写背景,后人不知为何要决策 |
| 决策(Decision) | 选了什么、为什么选它 | 只写“选 A”不写“放弃了 B/C 的什么” |
| 后果(Consequences) | 带来什么正向与负向影响 | 只写优点不写代价,误导后续 |
| 假设(Assumptions) | 基于什么前提;若前提不成立决策应推翻 | 假设藏在脑里不写明,无法证伪 |
假设是最易被忽略也最关键的要素。没有显式假设的 ADR 只是一份结论备忘,无法在前提变化时触发重新决策。假设必须是可证伪的——能写出“若观察到 X,则假设 Y 不成立,决策需重评”。
supersede 与决策树
ADR 的演进不是覆盖旧文件,而是新建并链接:
ADR-007: 选用 MySQL 作为订单库(Accepted)
↑ 取代
ADR-012: 迁移到 PostgreSQL(Accepted)
↑ 取代
ADR-019: 订单库分库分表到 TiDB(Accepted)
每份新 ADR 顶部写“Supersedes ADR-XXX”,旧 ADR 顶部写“Superseded by ADR-YYY”。这棵决策树让团队看清:每次技术变迁的触发条件(哪个假设变了)、当时的权衡、放弃旧方案的代价。没有这棵树,技术栈演进就只剩“现在长这样”,失去变迁逻辑。
练习
选一个你所在系统的技术决策(如选了某框架、某中间件),按四要素写一份 ADR。重点检查:假设是否可证伪?后果是否包含负向影响?若找不到当初的上下文,就把“现在能推断的”写明并标注“上下文为事后重建”。
软件工程示例
某团队要为订单服务选缓存方案。朴素做法是开会拍板“用 Redis”。写 ADR 则是:
| 要素 | 内容 |
|---|---|
| 上下文 | 订单服务 QPS 峰值 5w,读多写少;现有本地缓存击穿风险;团队有 Redis 运维经验但无 Cluster 经验 |
| 决策 | 选用 Redis 主从版(非 Cluster);读穿透用布隆过滤 |
| 后果 | 正向:低延迟、团队熟悉、运维成本低;负向:单点风险需哨兵、数据量增长后需迁移 Cluster |
| 假设 | ① 订单数据量两年内不超 50G;② QPS 增长不超 3 倍;③ 团队能掌握哨兵运维。任一不成立则需重评 |
这份 ADR 半年后若数据量逼近 50G,假设①触发,团队知道该启动迁移评估而非继续加内存——这就是显式假设的价值。它与 因果与证据总览:从主张到可验证决策 的证据思维一致:假设即待检验的因果主张。
适用边界与误用
- 不必为每个小决策写 ADR:低风险可逆决策(如函数命名、内部工具选型)写 ADR 是负担。ADR 留给影响面大或不可逆的决策。
- ADR 不是审批流:它是记录而非审批工具;若变成走流程填表,就失去留痕价值。
- ADR 不替代设计文档:设计文档描述系统结构,ADR 只记录决策理由。
- ADR 不保证选对:它让选错后可定向复盘,而非保证不选错。
- 不要等决策完美才写:决策一旦做出就写,哪怕后续被取代——被取代的 ADR 同样有价值,它记录了“曾这样想过”。
关联笔记
- 决策与权衡总览:从选项到可审阅的承诺(决策三层结构中的“承诺”层)
- 决策矩阵与权衡:在多目标下显式取舍(ADR 的“决策”要素可用矩阵支撑)
- 可逆性与双向门:降低决策的不可逆成本(判断哪些决策值得写 ADR)
- Premortem 与预承诺:在行动前发现失败路径(ADR 的假设可由 Premortem 识别)
参考资料
评论 (0)