
很多技术债不是因为当初的选择一定错了,而是后来的人只看见了“现在这样”,却找不到“当时为什么这样”。ADR(Architecture Decision Record,架构决策记录)要解决的不是写更多文档,而是把关键选择、约束和取舍放进代码仓库,让团队在修改系统前能先理解背景。
ADR 适合记录什么
ADR 不应该变成会议纪要,也不需要覆盖每个实现细节。它适合记录会影响多人协作、部署方式、数据模型、依赖边界或运维成本的选择。判断一个决策要不要写 ADR,可以看它是否满足下面任意一条:
| 场景 | 是否建议写 ADR | 原因 |
|---|---|---|
| 改用新的消息队列或缓存组件 | 是 | 会影响部署、监控和故障处理 |
| 统一接口鉴权方式 | 是 | 涉及多个服务和安全边界 |
| 某个页面按钮颜色调整 | 否 | 属于局部 UI 实现 |
| 为单个函数增加参数校验 | 否 | 代码本身就能说明意图 |
| 放弃一次重构计划 | 是 | “不做什么”同样需要被记住 |
如果三个月后有人可能重新争论同一件事,就值得写一份 ADR。
最小模板:短,但要有取舍
ADR 的价值不在篇幅,而在结构。建议每条记录控制在一到两屏内,写清楚背景、决策、备选方案和后果:
1 | # ADR-0007: 使用事件驱动同步订单状态 |
注意,“后果”不是只写优点。好的 ADR 会主动暴露代价,这正是后来维护者最需要知道的部分。

写 ADR 的流程
ADR 可以很轻量,但不能没有流程,否则很容易变成没人执行的静态文件。
建议使用下面这条路径:
- 提出者先写草稿,明确问题边界和约束条件。
- 在 Pull Request 中提交 ADR,像代码一样接受评审。
- 评审者重点看备选方案是否公平、后果是否写全、状态是否明确。
- 合并后将状态标为“已采纳”,并在相关代码、配置或运维手册中引用它。
- 当现实条件变化时,新建 ADR 替代旧记录,而不是静默修改历史。
第四步最容易被忽略。ADR 应该和实际实现互相指向:代码注释可以引用 ADR-0012,部署文档也可以链接同一条记录。新人读代码时,就能顺着入口找到背景。
状态要表达生命周期
每条 ADR 都应该有明确状态,避免读者猜测它现在还算不算数。常见状态可以保持简单:
| 状态 | 含义 |
|---|---|
| 提议中 | 还在讨论,不能作为实现依据 |
| 已采纳 | 团队决定按此执行 |
| 已替代 | 后续 ADR 改变了原决策 |
| 已废弃 | 决策不再适用,但历史保留 |
不要在旧 ADR 上反复覆盖结论。架构演进本来就是时间序列。如果某个决策被推翻,新建一条 ADR,并在旧记录里写清楚“被 ADR-00xx 替代”。

放在仓库里,而不是散在聊天记录里
ADR 最推荐和代码放在同一个仓库中,例如:
1 | docs/ |
这样做的好处很直接:评审流程一致,变更可以追踪,实验性决策不会提前污染主线。多仓库系统可以在平台仓库维护跨服务 ADR,再由具体服务引用编号。
文件名建议使用递增编号加英文短标题。编号让引用稳定,英文标题让路径可读。
常见误区
第一,ADR 不是用来证明自己正确,它应该记录当时可见的信息和约束。第二,ADR 不是越多越好;每个小改动都写,团队很快就会停止阅读。第三,线上故障可以先处理,再补写记录说明临时选择和清理计划。
还要避免抽象口号,比如“提升系统可用性”。更好的写法是:“将导出任务从同步 HTTP 请求迁移到后台队列,并把单次导出限制为 100 万行以内。”
落地清单
如果团队从零开始,先在仓库创建 docs/adr/,提交一个最小模板,再要求跨模块或跨团队的技术选择都通过 PR 合并。开始阶段不用追求格式完美,只要能回答四个问题:为什么决策,选择了什么,放弃了什么,代价是什么。
ADR 的目标不是制造文档负担,而是减少重复争论。把“为什么”写进仓库,系统的演进就不再只依赖少数人的记忆。