1.6

查看英文版

1.6 决策记录

概述与动机

决策记录是一份连同背景和后果一起记录某项重要决策的文档。其中最著名的形式是架构决策记录(ADR),一份简短的、原则上不可更改的笔记,记录一个具有架构意义的选择、做出该选择的原因,以及由此产生的后果。一个项目的全部记录集合就是它的决策日志(ADL),而持续维护这些记录的实践则属于架构知识管理(AKM)的一部分。本章建立在第 1.5 章决策制定与治理实践的基础之上,重点讲述如何在规模化场景下编写、存储和维护决策记录。

其动机很简单,却往往要经历痛苦才能领会。在任何长期运行的系统中,最昂贵的问题就是”这东西当初到底为什么这么建”()这个问题往往是几个月甚至几年后,由当初根本不在场的人提出的。代码展示的是系统做什么。测试证明系统能用。但两者都没有捕捉到你为什么在权衡并否决其他方案之后,选择了这条路径的原因。没有决策记录,这些思考理由就会随着人员流动而蒸发。团队会重新争论已经解决的问题,出于错误的原因推翻好的决策,或者出于恐惧而保留坏的决策。一份决策记录是写给未来的一封廉价家书,保存了当初的推理过程。

对于大型团队来说,这既是记忆辅助工具,也同样是协调工具。企业往往同时运行数十个团队,做出相互重叠的选择。一份共享的决策日志能把一个团队来之不易的推理过程转化为可复用的资产,并防止出现分歧、互不兼容的决策。在政府和受监管环境中,决策记录几乎是强制性的。审计人员、监督机构和后续承包商都需要一条可追溯的理由链,把具有架构意义的需求与针对这些需求所做的选择联系起来。一份维护良好的决策日志,往往是一个系统能否被保证和审计的关键差别所在。

关键原则

  • 记录为什么,而不仅仅是做了什么。 背景和被否决的方案才是重点。
  • 一条记录对应一个决策。 让每条记录具体且自成一体。
  • 小而轻胜过全面而无人使用。 一份存在的单页记录,胜过一份永远不会写出来的报告。
  • 一切都加上时间戳。 成本、约束和供应商都会变化;给每一项主张标注日期。
  • 务实地倾向于活文档。 不可更改是理想状态;实践中,用带日期的说明来修订即可。
  • 用词语而非缩写。 “决策”比”ADR”更能吸引人参与贡献。
  • 让决策可被发现,并在可能的情况下可被测试。 在合适的时刻呈现合适的记录;用适应度函数来保证它。

建议

捕捉基本结构

一份好的决策记录包含几个基本部分。采用一个已知的模板,而不是自己发明一个:

  • 标题: 一个简短、现在时、祈使句式的短语(例如”对账本使用 PostgreSQL”)。
  • 状态: 提议中、已接受、已被取代、已弃用。
  • 背景: 使这项决策成为必要的情境、各种力量、业务优先事项和约束条件;包括它所应对的具有架构意义的需求。
  • 决策: 明确陈述所做出的选择。
  • 后果: 哪些事情会变得更容易、哪些会变得更困难,随之触发的后续决策,以及所接受的风险。

流行的模板包括 Michael Nygard 的模板(简单且被广泛采用)、Tyree 和 Akerman 的模板(更为详尽,带有加权的备选方案)、MADR(Markdown Any Decision Records,在选项及其优缺点方面表现突出),以及 Y-statement(一种单句结构化形式)。在组织内统一采用一种模板,使各条记录具有可比性。可复制粘贴使用的模板见第 12.3 章。

编写具体、带日期且近乎不可更改的记录

让每条记录只对应一个决策。给每一项主张标注时间戳,尤其是会随时间漂移的内容:定价、扩展规模数字、供应商能力、许可条款。理论上,一条记录应当是不可更改的。当决策发生变化时,你应当撰写一份新的记录来取代旧记录,从而保留历史。而在实践中,许多团队发现活文档方式效果更好:在现有记录中插入新信息,并加上日期戳和一条说明,标明这是在决策做出之后才出现的信息。两种方式都是合理的。不可更改的风格在审计留痕方面更有力;活文档风格则更适合日常团队知识管理。要有意识地做出选择,并保持一致。

把记录存放在工作发生的地方

把决策记录放进与代码同处的版本控制系统中:建立一个 decisions/(或 adr/)目录,存放 Markdown 文件,一条决策对应一个文件,文件名采用小写、以连字符分隔的祈使动词短语(如 choose-database.md、format-timestamps.md)。这样可以免费获得历史记录、评审和差异对比功能,并让推理理由紧挨着它所解释的内容。如果你的团队更喜欢使用 wiki、Google Docs,或类似 Jira 的跟踪工具,也可以改用那些工具。工具本身远不如习惯重要。一个轻量级的命令行工具(例如 adr-tools)可以帮助搭建和索引记录。

把它们命名为”决策”,并把范围扩展到架构之外

许多团队总结出的一条实用经验是:措辞很重要。有些开发者和管理者一听到”架构”这个词就会抵触,而”记录”这个词也容易让人觉得是事后补的文书工作。仅仅把目录简单地改名为”decisions”往往就能扭转局面。团队会开始用同一套模板记录供应商选择、规划决策、排期决策、数据与合规决策等各种内容。人们从词语中学习的速度比从缩写中更快,而当整体框架是”帮助你未来的队友思考”而不是”填一份强制表格”时,人们也会更愿意参与贡献。

定义生命周期与治理机制

要让决策记录能够规模化,需要就周边流程达成一致(这正是第 1.5 章的治理理念落到实践的地方):

  • 谁可以发起一条记录,以及理由是什么: 通常是任何知情的贡献者都可以;当未来的开发者需要了解原因时就发起一条记录,而对于低风险、自成一体或已有文档记载的选择则可以跳过。
  • 生命周期: 一个简单的流程,例如启动 → 调研 → 评估 → 实施 → 维护 → 淘汰,并配有在各阶段之间流转的验收标准(问题已阐明、已考虑各种备选方案、权衡已有文档记录、已征询利益相关者意见)。
  • 角色: 提议者、调研者、评审者、批准者,以及一位负责定期(至少每年一次)复核记录并推动其最终淘汰的责任维护者。
  • 治理: 共识、冲突、升级和否决机制如何运作,以及任何合规性约束。可以借鉴行动优先和存异并行等原则,并把更重的流程留给不可逆、影响范围大(“单向门”)的决策。

让决策可测试、可被发现

决策记录记载一项决策;适应度函数则保证它:这是一种在持续集成(CI)中运行的自动化检查,用来验证该决策是否依然成立(例如”所有状态变更都必须发出事件”、“任何模块都不得跨越这些边界进行导入”,可借助 ArchUnit 之类的工具实现)。这把治理从周期性的人工评审,转变为持续的、可规模化的强制执行,这一点对于满足监管和审计目标(第 3.1、4.6、8.5 章)尤其有价值。接下来,要在合适的时刻呈现正确的记录。当开发者接触到某段受某条记录治理的代码时,能自动把相关决策附加到拉取请求上的工具,远胜过寄望于大家去读一个文档文件夹。

权衡:优缺点

选择优点缺点
轻量级 ADR(Nygard/MADR)编写速度快,确实能被写出来;仪式感低对于高风险、有争议的决策严谨性不足
重量级模板(Tyree-Akerman)带加权备选方案;适合重大、代价高昂的选择速度较慢;可能抑制日常记录习惯
不可更改 + 取代审计留痕清晰;历史得以保留记录数量增多;读者需要追溯链条
活文档(带日期的修订)单一的当前真相来源;易于维护审计留痕较弱;存在悄悄修改的风险
仓库内 Markdown有版本记录、可评审、紧邻代码对非开发者不够友好
Wiki / 文档工具各种角色都能访问历史记录和评审能力较弱;容易与代码脱节

核心张力在于严谨性与采用率之间的权衡。最严谨但无人使用的体系,记录不了任何东西。最轻量但人人都在用的体系,其价值会不断复利累积。默认采用轻量方式,并把更重的流程留给那些为数不多、代价高昂且难以逆转的决策。

与团队讨论的问题

  1. 合适的决策记录将如何在开发者接触到它所治理的代码的那一刻送达他们,而不是静静躺在一个没人打开的文件夹里? 一份只写不读的日志所记录的推理理由永远不会改变任何行为,这是决策记录失败的最常见方式:它们存在,但在真正需要的时候却没人去读。与之相抗衡的考量是投入的精力,因为自动呈现记录(在有人编辑受治理的代码时把相关记录附加到拉取请求上)需要投入工具建设,而 wiki 或文档文件夹并不需要这些投入。把证据带到讨论中来:最近有人推翻或重新争论一个已解决的问题时,相关记录在那一刻是容易被发现的,还是被埋没了?对于运行着数十个团队的大型组织来说,可发现性正是把一个团队来之不易的推理转化为可复用资产、而不是私人档案的关键。决定是否把记录存放在与代码同处的版本控制中,并将其接入拉取请求流程,这样记录就会出现在工作实际发生的地方。

  2. 每条记录由谁负责维护,又是什么机制防止你的日志逐渐腐化为一堆看似可信却已过时的错误信息? 决策日志最危险的失败模式不是一个空文件夹,而是一个装满记录、但其中的成本、供应商能力和约束条件早已在多年前悄悄过时的文件夹。每条记录都需要一位负责的所有者,按一定节奏(至少每年一次)复核它,并推动取代或淘汰工作,否则日志就会腐化为人们选择性引用、却很少信任的传说。把证据带进讨论:你的记录中有多少没有标注日期,有多少描述的供应商或价格已经发生变化,以及每条记录最近一次被复核是什么时候。在政府和受监管环境中,这一点更为突出,因为一条不可更改、层层取代的记录链,正是审计人员和后续承包商赖以获得可追溯理由的依据。明确地设定你的生命周期,给会漂移的具体主张打上时间戳,并指定维护者,这样日志才能保持为一份活的资产,而不是一片坟场。

  3. 你是否应该在所有团队中统一采用同一种模板,你那些风险最高的决策究竟需要多高的严谨程度? 可比性是一项实实在在的好处:当每个团队都使用相同的结构(Nygard、MADR 或类似模板)时,一个新团队可以找到此前的三条记录,用一个下午就采纳其中的推理,而不必花一个月去争论。核心张力在于严谨性与采用率,因为无人使用的最重模板什么也记录不了,而人人都在用的最轻模板则会不断复利累积价值。把证据带进讨论:记录是否真的被写出来了?另外,有没有哪些重大、有争议、代价高昂的决策,因为轻量形式跳过了对备选方案的权衡而分析不足?对于需要在各团队间协调重叠选择的企业而言,一份共享模板加上一个可搜索的索引,能够防止出现分歧、互不兼容的决策。对常见情形默认采用轻量方式,并提前就哪些”单向门”决策需要采用带加权备选方案的重量级形式达成一致。

  4. 究竟是什么在真正证明发起一条决策记录是合理的,又是谁有权说某个选择不需要记录? 门槛设得太高,重大选择背后的推理就会蒸发;门槛设得太低,日志就会被琐碎内容填满,把人们真正需要的记录淹没掉。对于大型组织来说,标准不清晰意味着每个团队都会各自临场发挥,导致覆盖程度参差不齐,也没有人能确信记录的缺失就意味着这是一个不重要的决策。把证据带进讨论:找出几个最近被记录下来、但其实本不必记录的决策,以及几个没有被记录、后来却让你付出了重新发现代价的痛苦案例。就一条朴素的判断标准达成一致,例如:当未来的开发者需要了解原因时就记录,而对于低风险、自成一体或已有文档记载的选择则跳过。在受监管和政府环境中,这个算法会发生变化,因为审计要求可能规定,无论团队是否认为值得记录,每一项具有架构意义的需求都必须留有记录,因此要提前明确哪些决策是不可协商、必须记录的。

  5. 你的记录是在决策那一刻真实捕捉的推理过程,还是事后为了应付某项要求而补写的文书工作? 一份为了结项而事后补写的记录,往往会把最终选定的方案”洗白”,并悄悄省略掉真正被权衡过的备选方案()而这恰恰是未来读者最需要的信息。与之相抗衡的压力是真实存在的:在决策之前或过程中写下原因,感觉上比直接交付要慢,而在书面材料中承认曾经被否决的路径,需要一些团队所缺乏的心理安全感。把最近的一些记录样本拿到桌面上,诚实地判断其中的背景和被否决的备选方案,读起来像是真实的深思熟虑,还是事后补造的辩护。对大型团队而言,空洞的记录比没有记录更糟糕,因为它们会让人们学会不再信任这份日志。在企业和政府审计中,这种区别尤为鲜明:监督机构和后续承包商所依赖的,是能真实反映当初考量过程的理由,而一份读起来像是表演的记录,会削弱这份日志本应提供的保证价值。

  6. 在你那些风险最高的决策中,哪些可以用自动化的适应度函数来加以保证,而不是寄望于周期性的人工评审能够发现违规? 决策记录记载的是一项选择,但只有在持续集成中运行的自动化检查,才能在多年间无数开发者接触这段代码的过程中,防止这项选择被悄悄侵蚀。这里的权衡是投入,因为编写和维护适应度函数(借助 ArchUnit 之类的工具)需要花费工程时间,而且许多决策,尤其是流程或供应商方面的选择,根本无法进行机械化测试。把证据带进讨论:有哪些边界决策(模块依赖关系、事件发出规则、数据访问规则)曾经被悄悄违反,直到评审或生产环境中才被发现。对于运行着众多团队的企业而言,适应度函数能把治理从一个中心化的瓶颈,转变为可规模化、又不拖慢每个人节奏的持续强制执行机制。在受监管和政府环境中,一项自动化、持续运行的检查,远比评审上的一个签字更有力的审计证据,因为它证明的是这项决策在今天依然成立,而不只是曾经有人批准过它。

行业视角

初创企业。 只保留这一习惯,别的都不要:在主仓库里建一个 decisions/ 文件夹,每当你做出一个未来的自己会质疑的决定时,写一份两段式笔记(背景和选择)即可。生命周期、角色和批准者统统跳过,因为你维持不下去的流程,就是你迟早会放弃的流程。哪怕只有一条记录,能让你的第一位新员工不必再问系统当初为何这样搭建,就已经值回了这项实践的全部成本。

小型企业。 没有专职架构师,时间也有限,就把记录放在团队已经在使用的地方,无论是 wiki、共享文档还是仓库本身,而不必购买专门的工具。习惯远比工具本身重要,所以要降低门槛:把目录命名为 decisions 而不是 adr,并把供应商选择和自建 / 购买的抉择,与技术决策一并顺手记录下来。当你依赖外部承包商时,一份简短、带日期、说明当初为何选定某个供应商或平台的记录,是防止日后被锁定在一个没人能解释清楚的选择上的廉价保险。

企业。 这里的工作核心是跨众多团队的协调:统一采用一种模板,发布一个可跨团队搜索的索引,并用适应度函数为关键的边界决策提供支撑,让违规行为在构建阶段就失败,而不必等到评审时才被发现。为每条记录指定一位负责的维护者,并设定复核节奏,让日志保持为一份活的资产,而不是逐渐腐化为传说。做得好时,一个团队在某个艰难选择上的推理,会成为下一个团队一个下午就能采纳、而不必重新争论的资产。

政府。 采购规则、透明度和公共问责,使决策记录几乎成为强制性要求。要求为每一项具有架构意义的需求保留一条不可更改、层层取代的记录,每条记录都将所做选择与其所满足的授权要求或合规控制关联起来,这样监督机构看到的是一条可追溯的理由链,而不是事后的重建。由于公共系统往往跨越多年、涉及多个供应商的生命周期,一份维护良好的日志,往往正是让后续承包商能够理解系统为何是当前这个样子、并在不重新争论已解决问题的前提下继续工作的关键所在。

示例

初创企业。 一家五人规模的初创公司在主仓库中添加了一个朴素的 decisions/ 文件夹,每当有人做出一个未来的自己会质疑的决定时,就写一份两段式笔记(背景和选择)。没有生命周期,没有角色划分,也没有批准者:只有把原因写在代码旁边的这个习惯。六个月后,公司的第一位新员工入职,她用一个小时读完整个文件夹,从此不再问”这东西当初为什么这么建”。这份轻量级日志每条记录只花几分钟,却让他们免除了团队规模尚未扩大之前,就会咬上一口的”重新发现税”。

企业。 一家拥有 30 个工程团队的零售商,在各自仓库中统一采用 MADR 格式的记录,并配有一个可搜索的中央索引。当一个新团队面对”monorepo 还是 multirepo”的问题时,他们找到了三条包含背景和后果的历史记录,用一个下午就采纳了其中的推理,而不必花一个月去争论。关键的边界决策(服务归属、数据访问规则)由 ArchUnit 适应度函数提供支撑,因此违规会导致构建失败,而不是等到评审时才被发现。这就是无需中心化瓶颈也能规模化的治理方式。

政府。 某机构在对一套福利系统进行现代化改造时,要求为每一项具有架构意义的需求编写一份 ADR,每条记录都把该决策与其所满足的授权要求或合规控制(无障碍性、数据驻留、可审计性)关联起来。记录不可更改、且层层取代,从而生成一条能满足监督评审要求的可追溯日志。至关重要的是,它还能让后续承包商理解系统为何是当前这个样子,从而在公共项目常见的跨越多年、涉及多个供应商的生命周期中保持连续性(第 4.6、10.4 章)。

商业理由:动机、投资回报率与总体拥有成本

一条决策记录只需几分钟就能写出来,再花几分钟就能完成评审。它的回报在于避免了重新决策的成本和避免了错误推翻的成本,这两项成本在长期运行的系统上都相当可观且反复发生。每当一个团队重新争论一个已经解决的问题,或者因为没人记得背后的约束条件而推翻一个本来合理的决策时,他们付出的代价往往是资深工程师的时间,通常还会引发一次事件。一份决策日志把这种反复出现的税负,转变为一次性的书写成本。

在总体拥有成本方面,决策记录是你能维护的杠杆效应最高的文档之一,因为它瞄准的是那项对人员流动最敏感的单一资产:理由本身。新员工入职会更快(新员工读到的是为什么,而不只是代码本身)。现代化改造会更安全(第 3.6 章:你能够分辨哪些决策是本质性的、哪些只是附带产生的)。审计成本会更低(证据早已存在)。而不保留这些记录的成本,在任何仪表盘上都是不可见的,并且会随着每一次人员离职而悄悄复利累积。要向领导层说明这一点,可以指出最近一次代价高昂的重新发现事件,或者一次因推翻决策而引发的事件,并指出建立这项实践的成本几乎可以忽略不计。

反模式与陷阱

  • 只记录做了什么而不记录为什么: 省略了背景和被否决的备选方案,而这恰恰是整个实践的意义所在。
  • 事后补写的文书工作: 记录是为了应付某项要求而写的,而不是为了思考;这样的记录读起来很空洞,没有人会信任它们。
  • 多决策巨型文档: 一个谁也无法导航、也无法干净利落地取代的庞大单页文档。
  • 未标注日期的主张: 把曾经成立的成本和约束条件,当作永恒不变的事实呈现出来。
  • 悄悄修改: 在没有日期说明的情况下改变一项决策的历史记录,破坏了审计留痕。
  • 只写不读的日志: 记录被创建出来,却从未在真正相关的时刻被呈现出来,因此无法影响任何行为。
  • 缩写词门槛: 坚持使用”ADR”和”架构”这类字眼,从而抑制了参与贡献。
  • 没有生命周期: 记录从未被复核、取代或淘汰,逐渐腐化为错误信息。

成熟度模型

  • 第 1 级(启动): 决策存在于人们的脑海、聊天记录和提交信息中;记录是被动且临时的,理由随着人员流动而被系统性地遗失。
  • 第 2 级(发展): 一些团队会在有人想起来的时候保留记录,格式和模板各不相同;这一实践在各团队之间并不一致,也没有共享的日志、命名规范或流程。
  • 第 3 级(标准化): 统一采用单一模板、仓库内存储,以及一套明确定义的生命周期与治理机制(发起 / 跳过标准、角色、复核节奏),并在整个组织范围内被一致地记录和执行;记录会被复核和取代,而不是被悄悄修改。
  • 第 4 级(管理): 决策日志会依据基线进行度量:覆盖率(携带记录的、具有架构意义的决策所占比例)、新鲜度(在各自复核节奏内被复核的记录所占比例,以及未标注日期或已过时的主张数量),以及可发现性(相关记录实际到达修改了受治理代码的开发者手中的频率)。负责的维护者根据这些指标采取行动,基于证据而非轶事来取代过时的记录、弥合覆盖缺口。
  • 第 5 级(协同): 一份可跨团队搜索的决策日志被融入日常工作:相关记录会在它所治理的变更上自动呈现,关键决策由持续集成中的适应度函数加以保证,日志作为一份活的资产,为新员工入职、现代化改造和审计工作提供支撑。组织持续改进这项实践本身,随着系统及其约束条件的变化,淘汰、取代和重新界定记录的范围,并随着决策组合的增长,重新调整投入严谨程度的重点领域。

讨论思路

  1. 你的团队上一次因为没人记得最初的理由而推翻或重新争论某个决策,是在什么时候?
  2. 把你的 adr/ 目录改名为 decisions/,会改变谁来参与贡献、什么内容会被记录下来吗?
  3. 你那些关键决策中,有哪些今天就可以用自动化的适应度函数来加以保证?
  4. 不可更改并取代,还是活文档:哪种方式更适合你的审计义务和团队文化,为什么?
  5. 一位新员工(或后续承包商)目前会如何发现你的系统为什么是当前这个样子?
  6. 在你的团队中,发起一条决策记录的理由是什么,不发起一条记录的理由又是什么?

关键要点

  • 一条决策记录记载一项重要决策及其背景与后果:为什么,而不仅仅是做了什么。
  • 让记录具体、带时间戳、轻量化;统一采用一种模板(Nygard、MADR 或类似模板)。
  • 把记录存放在与代码同处的版本控制中;可以考虑将其命名为”决策”,以扩大参与贡献的范围。
  • 定义生命周期与治理机制(发起 / 跳过标准、角色、复核节奏);把重量级流程留给单向门决策。
  • 让决策在变更发生的那一刻可被发现,并在可能的情况下通过适应度函数实现可测试性。
  • 投资回报体现在避免了重新发现和错误推翻的成本;在人员流动、现代化改造和审计最为重要的场景中,总体拥有成本的理由最为有力。参见第 1.5 章(决策制定与治理)和第 3.1 章(架构基础)。

参考文献与延伸阅读

  • Michael Nygard,《Documenting Architecture Decisions》(2011 年):奠基性的轻量级 ADR 方法。
  • MADR:Markdown Any Decision Records 项目(adr.github.io/madr)。
  • Jeff Tyree 与 Art Akerman,《Architecture Decisions: Demystifying Architecture》(《IEEE Software》,2005 年)。
  • Olaf Zimmermann,《Y-Statements》与《Architectural Decision Making》(ozimmer.ch)。
  • Joel Parker Henderson,《Architecture Decision Record (ADR)》:模板、示例与团队协作指南(github.com/joelparkerhenderson/architecture-decision-record)。
  • ThoughtWorks 技术雷达:“Lightweight Architecture Decision Records”。
  • Neal Ford、Rebecca Parsons、Patrick Kua、Pramod Sadalage,《Building Evolutionary Architectures》(适应度函数)。
  • AWS Prescriptive Guidance,“ADR process”;Red Hat,“Why you should use ADRs”。
  • Wikipedia,“Architectural decision” 与 “Architecturally significant requirements”。