老周家有一本用了五年的旅行手账。每次出游,他都会翻开新的一页:这次为什么选高铁不选飞机?为什么订这家民宿、放弃隔壁那家评分更高的?到了目的地遇到隔音差的房间,他也会补记一句“下次避雷”。五年后亲戚问起“你们怎么老往云南跑”,老周翻着手账,三分钟讲清了每一次选择的来龙去脉。 技术团队也需要这样一本“手账”。很多项目做到后期,没人说得清当年为什么选了 A 方案而不是 B 方案——拍板的架构师离职了,聊天记录沉到群底了。ADR(Architecture Decision Record,架构决策记录)就是技术团队的旅行手账:在做出重要技术决策时,把背景、备选方案、最终选择和预期结果写成一篇结构化短文,让决策有据可查、有迹可循。本文借老周家的手账,带你学会 ADR 的写法、模板与团队实践。

岗位关联

学完本文,你将能把团队里“拍脑袋”的技术选型,变成可追溯、可复盘、可传承的决策资产——这是技术负责人与架构师的核心软实力。

目录
  1. 可视化演示
  2. 认识ADR——给决策写“旅行手账”
  3. 编写时机——什么时候值得翻开手账
  4. 模板结构——手账的固定栏目
  5. 内容详略——写多细才算够
  6. 团队协作——全家一起写的手账
  7. 状态演化——手账要不断修订
  8. 工具实践——让写手账不费力
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示架构决策记录ADR的核心机制,通过全局/局部/动态/静态四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

架构决策记录ADR:ADR的 ADR的核心概念与价值 ADR的标准结构与模板 核心机制可视化 架构决策记录ADR整体架构与关键组件

认识ADR——给决策写“旅行手账”

老周家以前出去玩,全靠脑子记账:去年为什么去云南不去海南?当时选了哪家客栈、为什么没选隔壁那家性价比更高的?这些问题过两个月就成了一笔糊涂账。直到有一次,夫妻俩在返程路上为“当初到底是谁定的这家酒店”吵了一路。回家后老周立了规矩:每次出门,带上一本手账,把“为什么这么选”当场写下来。

技术团队遇到的是同一个问题。项目做了一年,新人问“为什么用这个数据库不用那个?”老人支支吾吾,翻聊天记录翻到手酸。ADR(Architecture Decision Record,架构决策记录)就是给技术团队准备的“旅行手账”:它由 Michael Nygard 在 2011 年提出,主张用一篇半页到一页的短文,记录一次重要架构决策的背景、备选方案、最终选择和预期后果。

ADR 不是设计文档,不画架构图、不写接口定义,它只回答一个问题:“这件事当时为什么这么做?” 一篇规范 ADR 由四个要素组成,可以用一个集合简洁表达:

就像老周家手账的固定栏目——日期、目的地、选择、教训——ADR 的四个要素缺一不可。缺了状态,你不知道这条决策现在还算不算数;缺了背景,后人读不懂当时的处境;缺了决策,等于没记录;缺了结果,下次还会踩同样的坑。手账的意义不是“写下来”,而是“讲清楚来龙去脉”。

ADR={Status, Context, Decision, Consequences}\displaystyle \text{ADR} = \{ \text{Status},\ \text{Context},\ \text{Decision},\ \text{Consequences} \}
这篇公式表达的是:一篇 ADR 本质上就是“状态、背景、决策、结果”四个要素的集合,缺了任何一个,记录都不完整。
  1. 状态 Status表示这条决策记录当前处在哪个生命周期阶段,例如 Proposed(建议)、Accepted(已接受)、Superseded(被取代)。状态是读者判断“这条记录现在还算不算数”的第一入口。
    Status∈{Proposed, Accepted, Superseded, Deprecated}\displaystyle \text{Status} \in \{ \text{Proposed},\ \text{Accepted},\ \text{Superseded},\ \text{Deprecated} \}
  2. 背景 Context描述决策发生的环境和动因,比如业务规模、团队约束、技术现状。背景是后人理解“为什么会有这个选择”的土壤。
    Context={业务约束, 技术现状, 时间节点}\displaystyle \text{Context} = \{ \text{业务约束},\ \text{技术现状},\ \text{时间节点} \}
  3. 决策 Decision明确写下最终选定的方案,以及候选方案中被放弃的那些。决策是记录的心脏,但不能只写结论不写理由。
    Decision={选定方案, 备选方案, 选用理由}\displaystyle \text{Decision} = \{ \text{选定方案},\ \text{备选方案},\ \text{选用理由} \}
  4. 结果 Consequences预判这个决策带来的收益与代价,包括性能、成本、维护负担。结果是给未来踩坑的人留的“减速带”。
    Consequences={预期收益, 潜在代价, 后续行动}\displaystyle \text{Consequences} = \{ \text{预期收益},\ \text{潜在代价},\ \text{后续行动} \}

编写时机——什么时候值得翻开手账

老周家不什么都记。买瓶矿泉水、在路边摊吃了碗面,这些事从来不进手账。但凡是决定“坐高铁还是飞机”“订哪家民宿”“路线怎么排”这种影响整趟行程、又没法轻易反悔的事,他一定当场写一页。他说:“小事忘了不可惜,大事忘了才麻烦。”

团队判断“要不要写 ADR”,用的也是同一把尺子。不取决于决策看起来大不大,而取决于两个维度:影响范围(这个决策牵连多少个模块、多少个团队)和不可逆性(一旦落地,改回来的成本有多高)。两者相乘,就得到一个简单直觉:

实践中常见的触发信号包括:引入新的核心框架或数据库、调整系统拆分方式、改变 API 的设计约定、切换部署架构、确定大模型供应商。这些都是“影响大且难回头”的决策。建议决策前先写一版“拟议”(Proposed),评审通过后更新为“已接受”(Accepted)。先写后做,记录的是思考过程;事后补写,常常变成考古现场。

老周家还有个规矩:手账要在出发前写“计划页”,行程中补“实际页”,回来再写“总结页”。三页合起来才是完整的决策记录。ADR 也一样——决策前写草稿,决策时定稿,落地后补结果,这样的手账才经得起“半年后被人追问”。

L=S×I\displaystyle L = S \times I
影响范围乘以不可逆性,就是一次决策的“重量”。重量超过团队设定的阈值时,就该动笔。
  1. 影响范围 S(Scope)决策影响的模块数量、团队人数、用户规模。影响面越广,越值得留下记录。
    S={模块数, 涉及团队, 用户规模}\displaystyle S = \{ \text{模块数},\ \text{涉及团队},\ \text{用户规模} \}
  2. 不可逆性 I(Irreversibility)切换成本与回退难度。数据库迁移、框架替换这类“回头路很贵”的决策,I 分值很高。
    I=1回退难度\displaystyle I = \frac{1}{\text{回退难度}}
  3. 乘积判断 L(Level)当 L 达到阈值(比如团队约定“任何影响两个以上模块且半天内无法回退的决策必须记录”)时,触发写 ADR。
    L≥Lmin⁡⇒需记录\displaystyle L \geq L_{\min} \Rightarrow \text{需记录}

模板结构——手账的固定栏目

老周的手账本是从文具店买的标准格式:封面写年份,内页分“日期、路线、交通、住宿、花费、备注”几栏。栏目固定有什么好处?写的时候不用想格式,五分钟填完;读的时候知道去哪找信息,一眼定位。

ADR 也有两种主流“栏目”模板。第一种是 Nygard 的四段式:状态(Status)、背景(Context)、决策(Decision)、结果(Consequences)。状态说明这条记录处在哪个阶段;背景交代当时的处境和约束;决策写最终选择的方案;结果写预期的收益和代价。四段式结构完整,适合信息量大的复杂决策。

第二种是 Y-Statement 模板,它把四段式压缩成一句话,逼你把“为什么放弃备选方案”也说清楚。Y 式模板的核心句式可以写成:

两种模板的关系,就像手账的“整页格式”和“便签格式”。信息量大的决策用四段式慢慢写;快速达成的共识用 Y 式一句话带过。但无论哪种格式,都必须回答同一个问题:选择背后的理由是什么?栏目只是容器,理由才是灵魂。

鉴于 C, 我们选择 D, 放弃 A, 因为 R\displaystyle \text{鉴于 } C,\ \text{我们选择 } D,\ \text{放弃 } A,\ \text{因为 } R
这句话的意思是:把决策叙述成“因为背景 C,所以选了 D 而没选 A,理由是 R”,它强迫作者交代被否掉的方案。
  1. 背景 C(Context)用半句话交代决策的环境与动因,替代四段式中完整的“背景”段落。
    C=决策所处环境与约束\displaystyle C = \text{决策所处环境与约束}
  2. 选择 D(Decision)明确写下最终选定的方案,这是整句话的主干。
    D=最终选定方案\displaystyle D = \text{最终选定方案}
  3. 放弃 A(Alternative)点名被否决的备选方案。Y 式模板最狠的地方就在这一项——不交代放弃了谁,决策理由就不完整。
    A=被否决的备选方案\displaystyle A = \text{被否决的备选方案}
  4. 理由 R(Rationale)给出权衡依据,通常是成本、性能、风险、团队能力中的一项或几项。
    R={成本, 性能, 风险, 团队能力}\displaystyle R = \{ \text{成本},\ \text{性能},\ \text{风险},\ \text{团队能力} \}

内容详略——写多细才算够

老周刚开始写手账时走过弯路:把每天的流水账都往上记,车票号、每个景点门票、每顿吃了什么。结果手账厚得像本电话簿,出门根本不想带,回头也懒得翻。后来他明白,手账的价值在“关键时刻的关键信息”,不在厚度。

ADR 的详略也讲究平衡。写太粗,只有一句“选用了方案 A”,后人看了等于没看;写太细,把讨论过程、每个观点、全部代码示例塞进去,真正的决策理由反被噪音淹没。判断标准很简单:读者是“未来那位没参加过讨论的同事”,他能用半小时读懂并且不再来问你,就够了。

具体取舍上:必写——决策理由、被否方案、关键数字(成本、性能、团队熟悉度)、预期代价;不写——代码实现细节、接口定义、讨论过程的聊天记录。为什么这样取舍?可以用总成本模型来理解:

老周后来总结出手账的“三记三不记”:记理由、记数字、记教训;不记流水账、不记无关细节、不记还能从代码里查到的内容。ADR 也是一样——你省下的每一分钟写作时间,都可能在未来让十个人多花十分钟去猜。写的时候多花五分钟,读的人就能少花五十分钟。

C总=C写+N读者×C读+T×C维护\displaystyle C_{\text{总}} = C_{\text{写}} + N_{\text{读者}} \times C_{\text{读}} + T \times C_{\text{维护}}
一篇 ADR 的总成本等于写作成本、所有读者的阅读成本、以及随时间为累积的维护成本之和。最合适的详略度,是让这三项之和最小。
  1. 写作成本 C写作者撰写这篇记录的一次性投入。写得太细,这个数字会急剧膨胀。
    C写↑⇕篇幅\displaystyle C_{\text{写}} \uparrow \Updownarrow \text{篇幅}
  2. 阅读成本 C读每名读者理解这篇记录所花的时间,要乘以读者数 N。写得含糊,每个人都要来问一遍,成本翻倍。
    N读者×C读=团队总阅读负担\displaystyle N_{\text{读者}} \times C_{\text{读}} = \text{团队总阅读负担}
  3. 维护成本 C维护随着时间推移,过时信息需要修订、被取代记录需要更新,这个负担与时间 T 成正比。
    T×C维护=长期维护负担\displaystyle T \times C_{\text{维护}} = \text{长期维护负担}
  4. 总成本最小化最佳篇幅不是“越短越好”,也不是“越全越好”,而是让写作、阅读、维护三项之和最小的那个点——通常就是半页到一页。
    篇幅∗=arg⁡min⁡C总\displaystyle \text{篇幅}^{*} = \arg\min C_{\text{总}}

团队协作——全家一起写的手账

老周家手账能坚持五年,秘诀不是老周勤快,而是全家参与。手账放在客厅书架固定的位置,谁去旅行谁写,回来的人补一笔,下次出门前全家人传看一遍。爷爷会翻出三年前的记录说:“那家店涨价了,别去了。”

ADR 要真正在团队里活起来,也得靠“全家参与”的机制。首先是存放位置:和代码放在同一个 Git 仓库里(比如 docs/adr 目录),让记录和代码一起走变更流程,而不是躺在某个人的云笔记里。其次是命名规范:用编号加标题,例如 001-使用postgresql作为主库.md,保证顺序清晰、检索方便。

再者是评审流程:新的 ADR 通过 Pull Request 提交,团队评审通过后才允许合并,避免“一个人拍板、全组不知情”。最后是责任归属:每条 ADR 指定一个决策负责人(Decision Owner),他对这条记录的正确性和维护负主要责任。这套机制运转得好不好,可以用覆盖率来衡量:

老周常说,手账放在客厅而不是锁在抽屉里,才是它价值最大的时候。ADR 也一样——躺在个人笔记里的决策记录,只是备忘录;放在团队共享仓库、经过全员评审、随时被检索引用的决策库,才是组织记忆。一个人记的叫日记,一家人写的才叫家史。

η=N已记录N应记录\displaystyle \eta = \frac{N_{\text{已记录}}}{N_{\text{应记录}}}
覆盖率等于已记录的 ADR 数量除以应该记录的决策数量。这个比值越接近 1,团队的决策可追溯性越好。
  1. 已记录 N已记录仓库中实际存在的 ADR 数量,来自命名规范的编号统计。
    N已记录=#{ADRi:i∈Repo}\displaystyle N_{\text{已记录}} = \#\{ \text{ADR}_{i} : i \in \text{Repo} \}
  2. 应记录 N应记录按照“影响范围×不可逆性”标准应该写的决策数量,来自团队约定和复盘估算。
    N应记录=#{关键决策}\displaystyle N_{\text{应记录}} = \#\{ \text{关键决策} \}
  3. 覆盖率 η接近 1 说明该记的都记了,新人可以放心依赖决策库;明显偏低则说明记录机制还没落地。
    η→1⇔决策可追溯性良好\displaystyle \eta \to 1 \Leftrightarrow \text{决策可追溯性良好}

状态演化——手账要不断修订

老周家三年前去古镇,手账上写着“坐大巴,人均三十五元,车程两小时”。今年再去,古镇通了高铁。老周没有撕掉旧页,而是在旧记录旁边贴了一张便签:“2019 年方案;2022 年起改乘高铁,四十五分钟,八十元。” 旧记录留着,但谁都知道它已经过时。

技术决策也一样会过时。当年选择单体架构是合理的,业务变大后拆分微服务也合理——这不是“当初错了”,而是“环境变了”。ADR 通过状态字段表达这种演化:一条新记录被接受后,如果它取代了旧决策,就把旧记录标记为 Superseded(被取代),并链接到新记录,而不是把旧记录删掉。

这种状态流转可以用一个简单的箭头表示:

需要区分的是,Superseded(被取代)和 Deprecated(废弃)并不完全相同:前者是被新决策明确接替,后者是失去意义但可能没有接班人。团队还应该定期复盘决策库——比如每个季度过一遍状态,把长期没人引用的旧记录标记清楚,就像老周每年年末整理一次手账,把过期的门票和便签清理掉。

老周家的手账页脚写着这样一句话:“记录不撕毁,只修订。” ADR 的维护哲学也是如此——被取代的决策保留原样,只更新状态和链接,这样后人能看到“为什么当初那样选,后来为什么变了”。决策会过时,留痕永不过时。

Accepted→新决策被采纳Superseded\displaystyle \text{Accepted} \xrightarrow{\text{新决策被采纳}} \text{Superseded}
一条“已接受”的决策,在更新更优的决策被采纳后,会转入“被取代”状态。旧记录被保留并标记,同时指向新记录。
  1. 前置状态 Accepted被取代之前,这条决策曾经是团队认可的现行方案,它的历史价值不应该被抹掉。
    旧决策∈Accepted\displaystyle \text{旧决策} \in \text{Accepted}
  2. 触发条件“新决策被采纳”出现了更优方案,并且新方案走完评审流程被正式接受,旧决策才进入取代流程。
    ∃ 新ADR,status=Accepted∧supersedes=旧ADR\displaystyle \exists\ \text{新ADR},\quad \text{status} = \text{Accepted} \wedge \text{supersedes} = \text{旧ADR}
  3. 目标状态 Superseded旧记录保留原貌,状态改为 Superseded,并增加“被 XX 取代”的链接;读者一眼就能看出它已失效。
    旧决策∈Superseded,link→新决策\displaystyle \text{旧决策} \in \text{Superseded},\quad \text{link} \to \text{新决策}

工具实践——让写手账不费力

老周的笔友问他:“手账虽好,每次都要手写,不麻烦吗?” 老周后来换了方法:买了带标签的活页本,打印好固定栏目,出门前抽出对应的一页填上几行字,五分钟搞定。工具不重要,但“让记录变便宜”的工具很重要。

技术团队也一样。如果写一篇 ADR 要耗费半天,没人愿意写。好在生态里已经有现成工具:adr-tools 是经典命令行脚手架,运行 adr new 标题 就能自动生成带编号的 Markdown 文件,内置四段式模板;你还可以定制团队自己的 Y 式模板,甚至生成 Markdown 表格。工具把“从零开始排版”变成“填几个空”,记录成本大幅下降。

工具之外,配合工程实践更佳:ADR 文件随代码一起走 Git 评审,合并即生效;用 CI 脚本检查新提交是否包含 ADR、编号是否连续、状态字段是否合法;部分团队还会把 ADR 渲染成内部站点方便检索。工具对记录成本的压缩,可以用一个视角来理解:

老周说:“手账坚持下来的秘诀,是把它变得足够容易。” ADR 落地也一样——模板、命令行、CI 检查这些工具,都是在把“记录”这件事的成本降到团队愿意长期坚持的阈值以下。当写 ADR 比写一封邮件还快时,它就从一个“额外负担”变成了“顺手的事”。

t记录=t思考+t书写×(1−r工具)\displaystyle t_{\text{记录}} = t_{\text{思考}} + t_{\text{书写}} \times (1 - r_{\text{工具}})
写一条 ADR 的总耗时,等于思考时间加上书写时间乘以“1 减去工具节省率”。工具能压缩的是书写部分,思考部分省不掉。
  1. 思考时间 t思考权衡方案、梳理理由的时间,这是决策本身的成本,任何工具都无法替代。
    t思考=f(决策复杂度)\displaystyle t_{\text{思考}} = f(\text{决策复杂度})
  2. 书写时间 t书写手工排版、写模板、编号、建文件的时间,这部分是工具能优化的空间。
    t书写=模板录入+文件管理\displaystyle t_{\text{书写}} = \text{模板录入} + \text{文件管理}
  3. 工具节省率 r工具脚手架和模板自动化的比例。用上 adr-tools 和团队模板后,r 通常能到 0.7 以上。
    r工具=1−t工具后t手工\displaystyle r_{\text{工具}} = 1 - \frac{t_{\text{工具后}}}{t_{\text{手工}}}
  4. 总耗时 t记录当工具把记录成本压到“比写封邮件还快”时,团队才愿意长期坚持。
    t记录<t邮件⇒可持续\displaystyle t_{\text{记录}} < t_{\text{邮件}} \Rightarrow \text{可持续}

AI 应用场景

场景一:AI 平台技术选型

阶段:系统架构规划与基础设施决策

解决的问题:AI 团队在向量数据库、推理引擎、训练框架之间反复摇摆。传统做法是开会争论、拍板走人,三个月后没人记得当时的理由;一旦换人或被质疑,只能重开一场争论。用 ADR 把实测数据、成本、团队熟悉度与放弃的方案一次性写清,之后任何质疑都可以“查记录”而不是“开大会”。

具体案例:某 AI 团队在对比 Milvus 与 pgvector 时,通过 ADR 记录了压测延迟、单条成本、运维难度和放弃 pgvector 的原因(召回率不足),评审通过后状态转为 Accepted。三个月后新同事质疑选型,负责人抛出一条 ADR 链接,十分钟化解争论。

场景二:大模型 API 与开源模型选型

阶段:模型选型与供应商评估

解决的问题:选闭源大模型 API 还是部署开源模型,牵涉效果、成本、数据隐私和法律合规,凭直觉决定容易反复推倒重来。ADR 把评测分数、单价、数据合规约束和放弃的方案记录在案,让选择有据可查、经得起审计。

具体案例:某金融科技公司评估接入大模型时,撰写“选择私有化部署模型而非调用公有云 API”的 ADR,记录了数据不出境的合规要求、GPU 成本测算和放弃闭源 API 的原因。半年后监管问询,ADR 直接作为选型依据的证据链。

场景三:新人融入与知识传承

阶段:团队协作与组织知识管理

解决的问题:骨干离职后,“训练管线为什么这样搭”“为什么先做规则后做微调”成了黑箱。传统靠口头传帮带,人走茶凉。ADR 把历史决策沉淀成新人自助阅读材料,把“问老师傅”变成“查决策库”。

具体案例:某公司 AI 团队沉淀了 30 条 ADR,覆盖技术栈、数据策略、模型评估口径。新入职算法工程师通过一周阅读,即可独立说明每条决策的背景和取舍,两周内参与评审不再“怯场”。

Python 演示

python90 行
"""ADR 管理工具:创建、查看和更新架构决策记录文件。"""import osimport refrom datetime import date  class ADRStore:    """维护一个 ADR 目录,提供创建、展示、状态更新能力。"""     def __init__(self, directory):        self.directory = directory        self.records = []        if not os.path.exists(directory):            os.makedirs(directory)        self._load()     def _load(self):        for name in sorted(os.listdir(self.directory)):            if name.lower().endswith('.md'):                self.records.append(name)     def _slug(self, title):        text = re.sub(r'[^\w\u4e00-\u9fff]+', '-', title.strip()).strip('-')        return text[:20] or 'decision'     def _format(self, index, title, status, context, decision, consequences):        today = date.today().isoformat()        return '\n\n'.join([            '# ADR-{:03d}: {}'.format(index, title),            '## 状态',            status,            '## 背景',            context,            '## 决策',            decision,            '## 结果',            consequences,            '> 记录日期:' + today,        ])     def create(self, title, context, decision, consequences):        index = len(self.records) + 1        filename = '{:03d}-{}.md'.format(index, self._slug(title))        path = os.path.join(self.directory, filename)        content = self._format(index, title, 'Proposed', context, decision, consequences)        with open(path, 'w', encoding='utf-8') as f:            f.write(content)        self.records.append(filename)        print('已创建:', filename)        return path     def update_status(self, index, new_status):        target = None        for name in self.records:            if name.startswith('{:03d}'.format(index)):                target = name                break        if target is None:            print('未找到 ADR-{:03d}'.format(index))            return        path = os.path.join(self.directory, target)        text = open(path, encoding='utf-8').read()        text = re.sub(            r'^## 状态\n\n.*$',            '## 状态\n\n' + new_status,            text,            flags=re.M,        )        with open(path, 'w', encoding='utf-8') as f:            f.write(text)        print('ADR-{:03d} 状态更新为 {}'.format(index, new_status))     def show(self, index=None):        if index is None:            for name in self.records:                print('-' * 30)                print(name)            return        for name in self.records:            if name.startswith('{:03d}'.format(index)):                print(open(os.path.join(self.directory, name), encoding='utf-8').read())                return        print('未找到 ADR-{:03d}'.format(index))  if __name__ == '__main__':    store = ADRStore('./adr')    store.create('引入Python作为服务端语言', '团队需要统一脚本技术栈', '选择Python 3.11', '生态丰富且招聘容易')    store.update_status(1, 'Accepted')    store.show()
目的:用 60 行代码实现一个迷你 ADR 管理工具,演示四段式模板生成、编号自动递增、状态流转和文档查看的完整闭环,可直接放进 docs/adr 目录使用。
  1. class ADRStore:定义一个管理 ADR 目录的类,负责创建、展示、更新三种核心操作,对应团队实践中的“存放、评审、演化”。
  2. self.records = []缓存目录下已有的 ADR 文件名,用于自动计算下一个编号,保证 001、002 连续不重号。
  3. self._load()初始化时扫描目录,把已有的 .md 文件读进内存列表,让“编号递增”建立在真实文件基础上。
  4. def create(self, ...)创建一条新 ADR,自动生成文件名、写入四段式模板内容,并登记到记录列表。
  5. index = len(self.records) + 1用已有记录数推导下一条编号,模拟 adr new 的自动编号能力。
  6. self._format(...)按“状态、背景、决策、结果、记录日期”的结构拼装 Markdown 内容,对应四段式模板。
  7. filename = '{:03d}-{}.md'.format(...)生成 001-xxx.md 格式的文件名,落实“编号加标题”的命名规范。
  8. re.sub(r'^## 状态\n\n.*$', ..., flags=re.M)只替换“## 状态”小节里的内容,正文其他地方出现同类词不受影响。
  9. store.update_status(1, 'Accepted')模拟评审通过,状态由 Proposed 变为 Accepted,演示“决策前后各写一稿”的状态流转。

常见误区

误区写 ADR 不就是把最终选定的方案记下来嘛?结论写清楚,其他都是多余的。我们团队一直这么干,也没出什么问题。

正解结论只是最后一行,真正有价值的是背景、被否掉的备选方案和权衡理由。老周的手账如果只写“选了高铁”,亲戚问“为什么”照样答不上来。ADR 的核心价值恰恰在于“为什么”,而不是“是什么”。只记结论的 ADR,等于没写的便签,还让人误以为决策有据可查。

为何常见:因为写结论最快最省事,而背景和备选方案需要回忆、整理和表达,人天生怕麻烦,容易把“记录了”当成“记录好了”。

误区我们团队小、项目简单,等做大了、人多了再写 ADR 也不迟。现在写纯属浪费时间。

正解判断标准不是项目大小,而是决策的影响范围与不可逆性。小团队引入一个数据库、定一套 API 规范,同样影响深远、同样难以回退。老周家第一次旅行也是小规模,但正是那次教训让他开始写手账。规模小的时候养成习惯,成本最低;等团队大了再补,历史决策早已散落各处。

为何常见:把 ADR 误当成“大公司流程文档”,忽略了它本质上是“决策思考的留痕工具”,与团队规模无关。

误区ADR 写好归档就完事了,它是历史档案,不应该再动。改了反而破坏记录的原始性。

正解ADR 有生命周期:Proposed(建议)→ Accepted(接受)→ Superseded(被取代)/ Deprecated(废弃)。被新决策取代时,要更新旧记录的状态并链接到新记录,而不是原样封存。老周手账里“大巴去古镇”的方案,在高铁开通后要贴便签更新,而不是当作永远正确的路线。保留历史不等于拒绝修订。

为何常见:把文档当档案,忽视了 ADR 是“活体知识”——它的价值在于不断反映决策的当前有效性,而不在于原封不动地保存。

误区把讨论过程、每个观点、全部代码示例都塞进 ADR,生怕漏掉任何信息。信息越多,后人理解越充分。

正解详略的衡量标准是“未来读者能否在半小时内读懂”。真正该记的是决策依据、关键数字、被否方案和预期代价;流水账式的过程记录反而让核心理由淹没在噪音里。老周的手账写过一年流水账后就被全家嫌弃,因为没人愿意在“车票号”“门票价”里翻找真正有用的经验。

为何常见:误以为信息量等于价值量,忽略了阅读成本和维护成本——过量的细节本身就是一种负债。

误区技术选型本来就是架构师拍板,ADR 当然也该由架构师独自完成。其他人问了也不一定懂,写了也白写。

正解决策影响整个团队,评审和补充需要全员参与。架构师负责“定稿”,但背景信息可能来自一线工程师,备选方案的缺点可能只有实际用过的人才知道。老周家的手账如果只让爸爸一个人记,妈妈和爷爷就永远不会主动翻它。ADR 变成个人自言自语,就没人读、没人信、没人维护。

为何常见:把“最终拍板的人”误解成“唯一应该记录的人”,混淆了决策权与记录责任。

面试题

Q1. ADR 的完整名称是什么?

单选题 · easy

  • A.架构决策记录← 正确答案
  • B.应用开发报告
  • C.自动化部署规则
  • D.高级数据仓库

答案:A。ADR 全称是 Architecture Decision Record,直译即“架构决策记录”,核心是记录“为什么选它”而不仅是“选了它”。B、C、D 都是相似的字母缩写干扰项,与架构决策这一主题无关。

Q2. 一篇 ADR 的核心四要素不包括哪一项?

单选题 · easy

  • A.状态标识
  • B.背景说明
  • C.代码实现← 正确答案
  • D.决策内容

答案:C。ADR 四要素是状态、背景、决策、结果。代码实现属于设计文档的范畴,正是 ADR 刻意不写的内容——就像手账不记每天的流水账一样,避免噪音淹没真正的决策理由。

Q3. 判断一个决策是否需要写 ADR,最合适的标准是什么?

单选题 · mid

  • A.方案由谁提出
  • B.影响与不可逆性← 正确答案
  • C.讨论持续多久
  • D.使用人数多少

答案:B。是否写 ADR 取决于影响范围与不可逆性的乘积,而非讨论时长、人数或提出者。就像老周只记录“影响整趟行程又没法轻易反悔”的大事,买瓶水这类小事从不进手账。

Q4. ADR 中被标记为 Superseded 表示什么?

单选题 · mid

  • A.决策被新纪录取代← 正确答案
  • B.决策被直接删除
  • C.决策还在评估中
  • D.决策已经过时作废

答案:A。Superseded 表示旧决策被一条新决策明确接替,旧记录保留并链接到新记录,就像老周在旧路线旁贴便签“已改乘高铁”。直接删除是错误做法;废弃但无接班人的状态则是 Deprecated。

Q5. ADR 记录应该放在哪里最合适?

单选题 · mid

  • A.团队共享的代码仓库← 正确答案
  • B.只有架构师的个人笔记
  • C.内部聊天工具群聊记录
  • D.项目验收的总结报告

答案:A。ADR 应放在团队共享的代码仓库,随代码一起走评审和版本管理,正如手账要放在客厅而不是锁在个人抽屉里。个人笔记、群聊记录、验收报告都无法提供持续可见、可检索、可追溯的团队入口。

Q6. 关于 ADR 的内容详略,下列哪种做法是正确的?

单选题 · hard

  • A.记录全部讨论过程与细节
  • B.只记录结论和最终代码
  • C.记录背景、理由与权衡结果← 正确答案
  • D.越简短越好不留理由

答案:C。ADR 应记录背景、理由、被否方案与权衡结果,既反对流水账式的过程记录,也反对只有结论的便签写法。判断标准是“未来没参加讨论的同事半小时内能读懂”,就像手账记关键数字而不记每顿吃了什么。

Q7. 采用 Y-Statement 模板写 ADR,核心句式是什么?

单选题 · hard

  • A.鉴于背景选择方案并放弃备选← 正确答案
  • B.因为成本低所以就选择了它
  • C.参考其他公司的最佳实践
  • D.按个人经验判断来做决定

答案:A。Y 式模板的句式是“鉴于 C,我们选择 D,放弃 A,因为 R”,它强制交代背景、选定方案、被否方案和理由四部分。只讲成本、只讲参考、只讲经验,都缺少“放弃备选方案”这个关键环节。

Q8. 新同事入职后看不懂历史 ADR,最可能的原因是什么?

单选题 · extreme

  • A.缺少背景和备选方案的描述← 正确答案
  • B.文档的编号命名不规范
  • C.ADR 没有被评审和批准
  • D.使用的 Markdown 语法过旧

答案:A。新人看不懂往往是因为记录只写了结论,没有交代当时的背景和被否掉的方案,导致他无法重建决策场景。编号、评审、Markdown 语法都不影响对决策理由的理解——就像手账只有结论没有原因,外人当然看不懂。

Q9. 下列哪些信号出现时,应该触发新写一篇 ADR?

多选题 · mid

  • A.引入新的核心框架或数据库← 正确答案
  • B.对既有架构做重大方向调整← 正确答案
  • C.修改某个按钮的文字颜色
  • D.更改代码中的变量命名

答案:AB。引入核心框架、数据库和调整架构方向都属于“影响范围大、难以回退”的决策,必须记录。修改按钮颜色和变量命名是低影响、易回退的小调整,写进 ADR 反而制造噪音,正如老周不会为买瓶水记一笔手账。

Q10. 团队维护 ADR 的常见最佳实践有哪些?

多选题 · hard

  • A.放在仓库由全员评审← 正确答案
  • B.被取代时更新旧记录← 正确答案
  • C.按编号加标题来命名← 正确答案
  • D.只允许架构师一人写

答案:ABC。ADR 的最佳实践包括放入共享仓库走全员评审、被取代时更新旧记录状态、按“编号加标题”命名保证可检索性。只允许架构师一人写会让记录变成个人日记而非组织记忆,违背了全家共同维护旅行手账的协作精神。