ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

知识跟着 Git 走:让架构决策与代码一起评审

知识跟着 Git 走:让架构决策与代码一起评审 一、半年后的那场重复讨论shop订单服务里有一个看起来奇怪的设计取消订单时审计事件不是发到消息队列而是和订单状态在同一个数据库事务里写入。新来的同事在评审里提出疑问——很多系统都是异步记录审计的这样同步写会不会拖慢接口要不要改成发消息会议室里没有人能给出确定的回答。有人说当初好像讨论过有人在聊天记录里搜了十分钟翻出三段没有上下文的对话一段提到事务内更安全一段说先这样吧还有一段是当时某位同事的个人判断。没有背景、没有替代方案的对比、没有当时的约束条件。讨论最后停在一句那我们再评估一下。这个场景每天在很多团队里发生。决策本身可能完全正确但它当时为什么这样定、在什么条件下会改已经没人知道了。于是每一次质疑都要重新走一遍讨论每一次讨论又会消耗几个人的时间而结论还可能和当初相反——不是因为新结论更好而是因为当初的理由丢了。这一篇给的做法是把架构决策写成简短的文件跟代码放在同一个仓库里走同样的评审流程。做完这一步上面那个问题会变成一句可以立刻回答的话“请查看docs/adr/0003-audit-events-in-transaction.md背景和修改条件都在里面。”二、先把几个词讲明白架构决策记录一份简短的文件记录在当时的情况下我们决定这样做原因是这个代价是这些什么时候应该重新考虑。它记录的是决策不是设计文档一份通常只有一页。决策做过选择的事情。它的标志是有替代方案并且被放弃了。如果一件事没有替代方案它是约束而不是决策写下来的价值不大。背景做出这个决定时面对的条件业务需求、技术限制、时间压力、团队规模、已有的系统。背景决定了决策的适用范围——条件变了决策就该重新看。后果这个决策带来的结果包括好的和不好的。把代价写清楚是决策记录里最容易被省略、也最有价值的部分因为它让后来的人知道这不是疏忽是有意接受的代价。修改条件出现什么情况时应该重新讨论这个决定。比如当日志量增长到单表写入成为瓶颈时“当外部系统开始依赖这个事件时”。跟代码走把决策记录放进代码仓库和代码一起提交、一起评审、一起被检索。相对立的做法是放在聊天记录、Wiki 或者某个人的文档里——它们和代码的距离太远最终会和现实脱节。评审改代码时改动和它对应的决策记录一起被审阅。这让为什么有机会被质疑而不是只能被接受。可检索性知道去哪里找。放进仓库的决策有固定的路径和编号可以被搜索、可以被引用、可以在代码注释里指向。聊天记录里的决策只对当时在场的人可检索。三、为什么知识要跟代码走3.1 决策的价值在于为什么而不是是什么代码本身已经记录了是什么取消订单时写数据库、写审计、发通知这些都能从代码里读出来。读不出来的是为什么为什么审计必须同步写、为什么不用消息队列、为什么这个取舍在当时是合理的。没有为什么的团队会遇到三种反复出现的成本。第一种是重复讨论每一个新成员都会提出同样的疑问讨论一次消耗几个人半小时。第二种是错误推翻后来的人不知道当初的约束贸然改成异步上线后才发现有的下游依赖了强一致。第三种是隐性依赖没人知道为什么这里不能改于是所有改动都绕着它走代码逐渐变得难以理解。决策记录解决的就是这三类成本。它把为什么从个人记忆变成团队的公共知识而且这份知识有明确的时间和环境——所以它不是永恒的规矩而是一个带有条件的判断。3.2 为什么聊天记录不行有人会说我们讨论都在聊天工具里有记录可以搜。这个说法在三种情况下失效。第一是上下文缺失。聊天记录里的问题是碎片化的事务内更安全这句话可能在回应别的问题也可能是在考虑另一个场景。把它抽出来当作决策依据比没有依据更危险——因为它看起来像证据。第二是关联断裂。代码改动时无法顺手把聊天记录一起改过一段时间聊天记录和代码的关系只存在于当事人的记忆里。反过来代码仓库里的决策文件会和代码一起被提交、一起被评审、一起被移动或者替换——它们的生命周期是绑定的。第三是缺少强制。写进仓库的东西要过评审评审会或者应该会问背景是什么、替代方案有哪些、代价是什么。在聊天里说一句话没有这层压力于是它也得不到应有的推敲。3.3 什么时候值得写一份决策记录不是所有决定都值得记录写太多会让文档变成噪声。以下是四个典型的判断标准难逆转的一旦落地改回来的成本很高。比如数据库结构、对外接口的语义、跨服务的事件契约。影响多人的不止一个模块或者一个团队会受影响。影响面越大越需要一份共同的依据。有争议的评审里出现了不同意见最终选了其中一个。记录争议和取舍能避免以后重新吵一遍。有外部约束的因为合规、预算、上下游系统的限制而做的选择。这类决策的共同特点是条件一变决策就可能变修改条件尤其要写清楚。反过来日常的实现选择用哪个循环、函数怎么拆不需要记录——它们要么容易被改要么不影响别人。判断方法是问一句如果半年后有人问为什么这样做我需要花五分钟以上解释吗需要就值得写。3.4 一页纸的结构决策记录的结构可以固定成六段加起来一页标题与编号、状态、背景、决策、后果、修改条件。如果当时有重要的替代方案再加一段考虑过的替代方案。写的时候有两个注意事项。第一只写一页决策记录的信息密度应该高背景三段以内决策两段以内后果分正负两栏列关键几条。第二用当时的语言写不要用现在看当时的想法是而要写当时的情况是……。这样它作为历史记录才是准确的至于现在怎么看可以在状态里更新被替代、废弃而不是修改原文。状态字段值得单独讲一份决策通常有四种状态——提议中、已接受、被替代、废弃。更新状态而不是删除文件是为了保留时间线后来的人能看到这条决策改变了什么而不是面对一片空白。3.5 决策与规则的关系决策记录和前面几篇讲的规则AGENTS.md 条目、测试、CI 检查不是同一层的东西但关系紧密规则是现在怎么做决策是为什么这样做、什么时候重新考虑。一条规则背后可能有一条决策也可能没有——判断方法是问如果规则这样定理由是什么能答出具体背景和取舍的值得记一份决策只能答一直这样的属于习惯不必强行编造理由。两者的联动点有两处。第一处是修改条件当修改条件被触发比如外部系统变了对应的规则要一起复核——可能是改动作可能是退役也可能是确认不变。第二处是冲突判断当有人质疑某条规则时先看它背后的决策是否还成立决策仍然成立就讨论实现决策不成立就先改决策再改规则。把这个顺序讲清楚可以避免很多规则之争变成情绪之争。还有一个常见情况值得提前说明有些规则没有决策记录是因为它来自外部约束标准、法规、上游系统的行为。这类依据同样值得记录一行——“依据RFC 9110 对幂等的定义”让后来的人知道这不是团队随手定的而是需要遵循的约定。写清外部依据也能防止因为不知道来源而随意放宽的情况。最后补一个对比帮助判断什么时候该写决策记录、什么时候该写规则条目。同样是取消流程要写审计事件这件事如果它只是团队约定写成规则条目就够如果它涉及取舍同步还是异步、写同一事务还是允许最终一致、失败时是否回滚就值得一份决策记录。经验法则是有代价选择的写决策没有代价选择的写规则。代价越明显决策记录的价值越高——因为未来的每一次改动都会面对同一组代价而记录让它们不必被重新发现。补充一条可以在三分钟内完成的检查下次评审里有人说这个不能改时用三分钟做一次检查问他不能改的理由是什么、这个理由现在还在吗。如果他答得出具体背景和条件说明这个不能改背后有一条真实的决策值得把它写下来如果答不出只有一直这样那就说明这条限制没有依据值得在这次改动里一起评估。三分钟的检查配合一份三十行的记录就是这一篇想留下的全部工作方式。写记录不是为了留档而是为了让下一次讨论从我们当初是不是讨论过变成这份记录写着三条修改条件我们逐条看。前者消耗的是几个人的时间和耐心后者消耗的是十分钟和一个目录。把这句话作为选择标准也很好用凡是会被反复问到为什么的设计都值得在仓库里留下这样一页。一页纸和代码在同一个仓库走同一次评审——这就是全部的方法。四、完整例子一份决策记录怎么诞生、怎么被引用4.1 决策的起点是一次真实的争论shop订单服务需要决定一件事取消订单成功之后通知给客服和下游系统怎么发出。两个方案摆在桌上。方案一是同步发送在取消用例里直接调用通知接口成功返回之后再响应请求。方案二是异步发送把通知写进消息队列由消费方处理。讨论中出现的关键事实有几条通知接口由另一个团队维护历史上出现过超时客服系统在绝大多数情况下需要立刻看到取消结果延迟十几分钟会带来额外的电话量而下游对账系统可以接受延迟。争论的焦点是为了接口的响应速度是否值得接受通知可能延迟的代价。最后的决定是通知在事务提交后同步发送发送失败不阻塞取消结果而是记录一条待重发记录。这是一个典型的折中决策既不为了通知牺牲接口可用性也不为了速度放弃通知的可靠性。4.2 写下来的那份记录决策落地时团队在仓库里新建了docs/adr/0007-cancel-notification-sync.md内容如下示例# ADR-0007取消订单后的通知采用事务后同步发送 状态已接受2026-09-29 ## 背景 取消成功后需要通知客服系统与下游对账系统。客服希望在操作后立即看到结果 对账可以接受小时级延迟。通知接口由外部团队维护历史上出现过超时。 ## 决策 在取消事务提交之后同步调用通知接口 - 通知失败不影响取消结果状态与审计已提交 - 通知失败时写入一条待重发表由定时任务重试 - 对账系统继续使用现有的事件消费方式不变化 ## 后果 正面客服实时可见接口语义清晰取消成功即通知已尝试。 代价取消接口的响应时间会受通知接口影响需要监控其超时率 待重发表会成为新的运维对象需要配套的重试与告警。 ## 考虑过的替代方案 - 全部异步实现简单但客服会有可见延迟电话量上升。 - 通知失败即回滚取消一致性更强但会让用户因通知系统抖动而无法取消订单。 ## 修改条件 - 通知接口的稳定性改善到可以接受同步失败重试时重新评估重试策略 - 客服系统改为主动轮询后本决策的核心前提实时可见不再成立 - 待重发表的规模持续增长、重试成为常态时重新讨论异步方案这份记录只有三十行左右但它回答了四个关键问题当时面对什么背景、决定做什么决策、付出什么代价后果、什么情况下重来修改条件。注意考虑过的替代方案那一段——它让后来的人知道这两个明显更简单的方案为什么被否决而不是重新提一遍。4.3 在 PR 里引用它决策记录如果只是躺在目录里仍然可能被忽略。让它生效的关键动作是在改动里引用它。同一周的 PR 描述里团队写了这样一段示例变更取消订单后新增通知调用与待重发表写入 决策依据docs/adr/0007-cancel-notification-sync.md 验证 - pytest -q tests/orders含 test_cancel_notification_failure_records_retry - ruff check . 风险通知接口超时会拖慢接口响应已加入超时监控指标 cancel.notify.timeout这段文字有三个作用。对评审者它提供了判断改动的依据——不需要从代码里猜意图对未来的人它在 git 历史里留下了这次改动对应哪条决策的线索对代理如果它后来要改这块代码它把为什么这样实现和可以改到什么程度一起给出了。代码里也可以留一行指向决策的注释但要克制只在实现看起来反直觉的地方写。比如通知调用失败不抛异常这一段注释可以写失败不进异常见 ADR-0007失败走待重发表。这类注释的价值是防止后来的人顺手修正一个看起来像 bug 的设计把它们改成抛异常而这类顺手修正恰恰是最容易破坏原有取舍的动作。4.4 半年后它是怎么被用上的回到开头的场景新同事质疑同步通知的设计。这一次回答不是当初好像讨论过而是三步动作。第一步找到记录。docs/adr/目录按编号排列0007就在那里搜索关键词通知也能命中标题和背景段。第二步核对条件。记录里写了三条修改条件逐条看现在的状态通知接口的稳定性是否改善有监控数据、客服系统是否改为轮询没有、待重发表是否成为常态没有。三条都不成立说明决策仍然适用。第三步给出结论并记录。结论是维持现状同时把这次讨论的结论作为一行追加到记录里示例“2027-03 复核三条修改条件均未触发保持不变”。追加而不是改写保持了这份记录的时间线完整。三步做完大约十分钟。对比第一次那场会议室里的重复讨论三个人的半小时还没有结论差别是显而易见的决策记录省下的不是文档时间而是每一次决策被重新讨论的时间。4.5 目录与命名的小约定让这套做法长期可用的是几个很朴素的约定。目录固定在docs/adr/文件名用四位编号 短横线标题编号递增不重复状态字段放在文件第一行便于用脚本统计被替代的记录不删除只改状态并写明由 ADR-00XX 替代。如果团队使用任务单和材料包前面几篇的做法决策记录和它们的联动方式很简单任务单里写一句本次改动涉及 ADR-0007 的修改条件需要复核材料包里把这份记录列进必给。这些约定的共同目标是让决策可被发现不管你是通过目录、搜索、还是代码注释找到它都能落到同一种格式、同一套字段上。可发现性比内容精美重要得多——一份写得很好但没人知道在哪的记录和没有记录的效果差不多。4.6 一次替代的完整流程假设一年后客服系统改成了主动轮询ADR-0007 的第二条修改条件实时可见是核心前提不再成立。这时该怎么做下面是四步示例1. 新建 docs/adr/0014-cancel-notification-async.md背景里写明 ADR-0007 的前提客服需要实时已不成立改为轮询 2. 在 ADR-0007 的状态行追加被 ADR-0014 替代2027-04 3. 代码改动与 PR 描述同时引用两份记录旧记录说明来由新记录说明方向 4. 把变更同步到相关材料任务单的范围、事件字段说明、监控指标说明流程里第 1 步和第 2 步的顺序很重要先建立新记录再改旧记录的状态。反过来操作会出现一小段时间旧的已被标记失效、新的还不存在而这段时间恰好是别人最需要依据的时候。还有一个细节值得保留新记录的背景里引用旧记录时写清因为哪一条修改条件被触发。这句引用让两条记录之间形成可追溯的关系——未来的人读到这里能顺着编号一直往前翻看到这个设计是如何一步步演变的。决策的历史因此不再是零散的文档而是一条有方向的链条。4.7 用一个脚本看目录的健康状态记录多了以后哪几份缺字段、哪几份状态该更新会变成一件靠记性的事。可以用一个十几行的脚本把它变成一次命令输出# scripts/check_adr.pyfrompathlibimportPath REQUIRED(## 背景,## 决策,## 后果,## 修改条件)forpathinsorted(Path(docs/adr).glob(*.md)):textpath.read_text(encodingutf-8)statusnext((lforlintext.splitlines()ifl.startswith(状态)),状态缺失)print(f{path.name}|{status})missing[hforhinREQUIREDifhnotintext]ifmissing:print(f 缺少:{, .join(missing)})在shop项目的决策目录上跑一遍输出示例$ python scripts/check_adr.py 0003-audit-events-in-transaction.md | 状态已接受2026-05-12 0007-cancel-notification-sync.md | 状态已接受2026-09-29 0009-retry-backoff.md | 状态已接受2026-06-02 缺少: ## 修改条件 0011-order-id-format.md | 状态被 ADR-0014 替代2027-04-02脚本不判断内容写得好不好它只挡住两类最常见的疏漏漏写修改条件以及状态忘了更新。两行输出足够让人在体检时直奔问题条目不需要把每份文件都读一遍。五、反例与代价五种写了等于没写的记录5.1 反例一只有结论没有背景做法记录里只写一句取消后的通知采用同步发送没有背景、没有替代方案。它为什么看起来能行文档看起来很简洁也确实记录了我们做了什么。最后的代价是它无法支撑任何判断。半年后有人问能不能改成异步你只能回答当初决定同步却说不出当初的约束是否还在。记录的价值几乎全部集中在背景和修改条件两段上——它们决定了这条决策还成不成立。一个实用的自检是只看背景和修改条件能不能推断出决策能说明背景写清楚了不能说明背景太薄。5.2 反例二写成一份设计文档做法一份记录写了十页包含接口定义、时序图、数据库表结构、部署步骤。它为什么看起来能行内容详实看起来很有价值。最后的代价是它不会有人读也不会有人更新。决策记录和设计文档的目的不同设计文档描述系统怎么构成决策记录解释为什么这样选。前者会随实现演进频繁变化后者在决策被替代之前基本稳定。把它们混在一起会导致记录随着代码改写而变旧最后连当时的决策是什么都读不出来了。控制在一页以内的另一个好处是写它本身的成本低因此团队真的会写。5.3 反例三把它放在代码之外的地方做法决策写进团队的 Wiki 或者共享文档理由是大家都会去那里看。它为什么看起来能行Wiki 有目录、有搜索、也能加评论看起来是知识管理的正规做法。最后的代价有三层。第一层是评审断裂改代码时不会顺手改 Wiki两者逐渐不同步第二层是关联断裂代码里无法引用一个稳定的路径注释里没法写见 XX 文档第几节第三层是检索困难代理和工具读不到它——它们能读仓库读不到你公司的 Wiki。把决策放进仓库付出的只是仓库里多一个目录换来的却是全流程可用。5.4 反例四写完从不更新状态做法三年前写的决策一直挂着已接受而它描述的做法早已被替换。它为什么看起来能行记录内容没有过期背景和决策都是历史事实所以看起来不需要动。最后的代价是误导。一份已接受的记录会被人当作现行约定有人据此拒绝合理的改动有人则因为发现它与现实不符而对所有记录失去信任——后者更糟。正确的做法是决策被替代时把状态改成被 ADR-00XX 替代并保留原文决策的前提消失时改成废弃并写明理由。两种更新都是一行改动却决定了这套机制的可信度。5.5 反例五为了记录而记录做法每个小决定都写一份记录——变量怎么命名、函数怎么拆、日志用什么格式。它为什么看起来能行体现我们很重视记录盘点的时候数量可观。最后的代价是噪声淹没了信号。当目录里有八十份记录时真正重要的那八份不再突出检索时也会被大量琐碎条目干扰。回到第三节的判断标准难逆转、影响多人、有争议、有外部约束——四条里至少占一条才值得写。对于日常实现选择代码本身就是记录谁改的、什么时候改的、怎么改的git 都有。六、落地步骤写第一份决策记录并让它活着第零步先定目录与编号规则。目录固定在docs/adr/文件名是四位编号加短标题编号递增、退役后不复用。为什么先做这件小事PR 描述、代码注释、任务单里都需要一个稳定的引用地址。没有编号引用只能写成那份讲通知的文档而这种说法在两份记录都涉及通知时就失效了。怎么检查随便挑一处引用注释或 PR 描述看它能不能直接打开对应的文件打不开说明地址还不稳定。第一步认领一个已经争论过的决定。不要从构建决策体系开始而是从最近一次评审里吵过的那件事开始。为什么因为它有现成的背景材料讨论记录、代码、数据写起来最省力收益也最直接。怎么检查你能不能说出这件事的两个替代方案——说不出来说明它可能不构成决策。第二步补齐背景与约束。写下当时的业务需求、技术限制、时间压力。为什么背景排第一因为它决定了修改条件没有背景修改条件写不出来。怎么检查背景段里有没有具体的数字或者事实延迟要求、调用量、外部系统的行为而不是为了更好的架构这种空话。第三步写决策用确定的语气。一句话说清决定做什么下面列 2 到 4 条具体做法。为什么不是倾向于或者建议因为决策记录描述的是已经生效的选择还没有定的东西属于讨论不属于记录。怎么检查读一遍问如果新人完全照做行为是否唯一。第四步分两栏写后果。一栏写正面结果一栏写代价与风险。为什么必须写代价因为不写代价的记录会让后来的人以为没有代价从而低估改动的影响而代价恰恰是审批和复盘时最需要的信息。怎么检查代价栏至少有两条且都是具体可观察的响应时间受影响、新增一张待重发表。第五步写修改条件。三条左右每条都是可观察的事件。为什么这一步是整份记录里最重要的因为它把永远正确变成了在当前条件下正确——这正是决策能被安全地重新审视的前提。怎么检查每条修改条件能不能对应到一个监控指标、一次外部变更、或者一个业务动作。第六步在 PR 里引用并评审它。把记录和代码一起提交在 PR 描述里写清决策依据和验证结果。为什么必须和代码一起因为这是让决策进入评审流程的唯一方式。怎么检查改动的 PR 描述里有没有指向记录路径评审记录里有没有人讨论过背景与代价。第七步维护状态与复核记录。每次复核追加一行结论日期 结论决策被替代时更新状态并写明替代者。为什么追加而不是改写因为时间线本身是信息。怎么检查目录里最新的一份记录能看出它最近一次被复核是什么时候。可复制的模板# ADR-四位编号一句话决策 状态提议中 / 已接受 / 被 ADR-XXXX 替代 / 废弃日期 ## 背景 业务需求、技术限制、外部约束3 段以内尽量带具体数字或事实 ## 决策 一句话 2..4 条具体做法 ## 后果 正面1..3 条 代价1..3 条包含新增的运维或监控负担 ## 考虑过的替代方案 - 方案 A优点但 为什么不选 - 方案 B优点但 为什么不选 ## 修改条件 - 可观察的事件 1 - 可观察的事件 2 ## 复核记录 - 日期结论七、常见问题问决策记录和代码注释有什么区别注释描述这段代码在做什么、注意什么决策记录描述整体上为什么选择这个方向、代价是什么、什么时候重新考虑。前者是局部的跟着函数走后者是全局的覆盖多个文件甚至多个服务。两者配合的方式是注释在反直觉的地方留一行指向决策编号“失败不抛异常见 ADR-0007”决策记录承载完整的背景。只写注释的问题是它没有地方放背景与替代方案只写记录的问题是读到具体代码时可能想不起它对应哪条决策——所以两处都留一点线索最稳妥。问谁来写决策记录谁推动了这次决策谁写。通常是提出方案并最终拍板的那个人技术负责人、模块负责人。但评审是所有受影响的人参与的——记录写完后过一遍评审重点看三处背景是否遗漏了关键约束、代价是否被淡化、修改条件是否可观察。写的人和评审的人不必重合重合也没关系关键是写完有人看。问记录应该写多详细一页以内各部分给一个上限背景三段、决策一段加三到四条做法、后果正负各一到三条、替代方案两条、修改条件两到三条。这些上限的作用不是限制思考而是逼你把最重要的信息留下。经验上一份记录如果超过一页通常说明两件事之一要么它包含了设计文档的内容拆出去要么这次讨论实际决定了好几件事情拆成多份记录。问如果决策后来被证明是错的怎么办不删原文改状态。做法是新建一份记录编号递增描述新的决策在旧记录的状态栏写被 ADR-XXXX 替代并保留旧记录的全部内容。这样做有几个理由错误决策的背景往往和正确决策的背景同样有价值它解释了当初为什么会误判保留时间线能让人看到团队的认识是怎么演进的删除会让引用它的代码注释变成悬空的线索。问小团队、快速迭代的节奏里这套做法会不会太重把模板压缩到最小三行也可以成立——背景一行、决策一行、修改条件一行状态用第一行。真正不能省的只有两项决策是什么以及什么情况下重新看。小团队的记录通常更短但收益反而更明显因为人少意味着每个人的记忆负担更重决策更容易只留在某一两个人的脑子里而人员流动哪怕是内部换岗会立刻让这些记忆消失。问怎么让新成员真的去用这些记录把读一遍相关记录变成上手任务的一部分给他一个具体改动比如把通知改成批量发送要求他在动手前找出相关的决策记录并说出三条修改条件的当前状态。这个过程会让他明白记录在哪里、怎么用。另一种更轻的方式是把记录目录放进 onboarding 文档的第一段并给出目录结构说明每份记录一行摘要。两种方式都在做同一件事让查记录成为默认动作而不是需要额外想起的事。问决策记录和任务单、材料包怎么衔接三者回答不同的问题任务单回答这次做什么、怎么验收材料包回答这次给哪些材料决策记录回答为什么这样做、什么时候该重新考虑。衔接方式有三种在任务单的范围里写涉及 ADR-0007 的修改条件需复核在材料包的必给里列入相关记录在交付物里追加一行复核结论日期 结论。三种方式都不增加新流程只是多写一两行却能把决策和当次工作绑在一起。问如果团队暂时没有评审流程这份记录还有价值吗有但价值会缩水。没有评审时记录仍然能解决找到背景的问题——至少未来的你或者代理能读到当初的取舍缩水的部分是背景被质疑的环节因为没有人会在写的时候被迫解释代价。弥补方式是自己给自己加一道检查写完记录后隔一天再读一遍专门找三处——有没有把代价写得比实际轻、修改条件是不是可观察、替代方案有没有漏掉明显的选项。这道自检的成本是十分钟能补上大部分评审的功能。问多份决策之间会不会互相冲突会而且这类冲突通常是发现问题的好线索。处理方式是引用而不是覆盖新记录在背景里明确说明它与旧记录的关系补充、收紧、替代状态栏里注明替代关系。检索冲突的方法很简单按主题搜索决策目录把涉及同一模块的记录列在一起读一遍看有没有两条对同一件事给出不同做法却都标着已接受。发现冲突时不要急着改代码先把两条记录摆出来对比修改条件——常常是其中一条的前提已经消失。问记录里的数字和事实要不要核实要至少核实那些会被用来判断修改条件的事实。举例背景里写通知接口的超时率是百分之几这个数字如果来自某个人的印象就会污染后续判断比如稳定性改善了吗这个问题将无法回答。做法是标注来源来自监控就写指标名来自外部团队就写确认人和时间来自估算就明确写估算。标注来源还有一个副作用是好的它会让你在写的时候意识到哪些事实其实没有依据从而先去补证据而不是把不确定性写进记录。问代理需要读这些记录吗需要而且它读得比人更彻底——只要记录在仓库里它就能在任务开始前把整个目录扫一遍。这正是跟代码走的另一个价值记录不只服务人类读者也服务那些需要理解项目约束的自动化流程。为了让代理用得上有两点可以注意标题里写清主题便于按关键词检索、修改条件写成可判断的句子便于它在改动前对照现状。如果团队在前面几篇里已经建立了材料包的习惯把相关记录列入必给是最省事的接法。问决策记录里要不要写谁决定的建议写但要写成角色而不是背锅人。格式上可以写决策者订单模块负责人评审支付与客服代表不用写个人姓名的场合就写角色。写它的价值在于两点一是让后来的人知道找谁了解当时的细节如果对方还在团队里二是让决策的责任归属清晰从而避免没人知道是谁定的所以谁也不敢改的状态。如果团队文化里写名字会带来压力就只写角色——关键是留下一个可以追问的入口。问如果一件事当初没有经过讨论是顺手定的现在还想补记录怎么办值得补但要如实写。背景里可以写本次决策源于实现过程中的临时选择补充记录以说明现状与风险。补记录的价值在于它把隐性选择显性化一旦写下来它的代价和修改条件就进入了团队的视野下一次有人要改这块代码时至少有依据。补记录时注意不要美化过程——把没有讨论写成经过充分评估会让记录的信用度打折而信用度是这套机制里最贵的资产。问修改条件怎么写才算可观察看它能不能对应到监控指标、外部变更或者业务动作这三类东西之一。写当系统变慢时不合格因为没有判定标准写当取消接口的响应时间在监控上持续高于约定阈值时就合格因为看指标就能得出结论。另外两类也一样外部变更写成当客服系统改为主动轮询后业务动作写成当日订单量超过单表写入能力时。写完自检一次把这条修改条件念给同事听问他今天成立了吗如果他的回答只能是看情况这条就还得再改。问决策被替代之后代码里指向旧编号的注释怎么办先判断注释里那句话还成不成立。如果它表达的约束仍然有效例如失败不抛异常见 ADR-0007可以保留旧编号同时在替代发生时把新编号一并写进去如果它表达的做法已经被换掉就在本次改动里把注释改成指向新记录。最怕的是留着一条指向已失效记录的注释却不说明它已失效——后来的人按注释找到旧记录会得出和现状相反的结论。替代流程里多做一步搜一遍引用旧编号的位置就能避免这类悬空线索。问决策记录里的日期重要吗重要而且是它区别于设计说明的关键之一。状态行带日期复核记录带日期替代关系标出时间三处合起来能还原时间线哪一年做的判断、哪一年复核过、哪一年被换掉。时间信息的用处很实际它让你在核对背景时知道当时面对的是什么条件团队规模、预算、上游系统的版本而不是拿今天的条件去评判几年前的取舍。缺少日期时记录读起来仍然通顺但你没法判断它的时效性。问记录和现行代码不一致时先信哪一个先信代码它描述的是现状记录解释的是原因。不一致通常来自两种情况记录没更新决策已经变了但状态和复核记录没补或者代码偏离了决策改动时没人注意到这条约定。两种情况的处理都不难关键在于别让它悬着。发现不一致时先判断属于哪一种然后选择动作补一行复核结论并更新状态或者按记录把代码改回来。如果两者都不合适说明前提真的变了——那就新写一份记录把旧的那份标成被替代。问项目跨多个仓库时记录放在哪个仓库放在这次决策主要影响、并且改动最常发生的那个仓库里。判断标准很直接执行者改代码时会不会顺手读到它。如果这条决策同时约束两个仓库就在主仓库建一份完整记录另一个仓库用一个简短文件指向它写明路径和编号避免两份内容各自演化。引用跨仓库时要注意地址的形式写在注释里的引用应该是人能顺着找到的路径而不是只对某个人有效的说法。这件事和前面讲的可发现性是同一个要求无论从哪里被找到最终都落到同一份原文上。问决策记录要多久复核一次不做定期全量复核改成两种触发式复核。第一种是修改条件被触发时任何一条修改条件成立就复核这份记录并在文件末尾追加一行结论。第二种是涉及该模块的改动发生时改动者顺手看一眼相关记录确认前提没变。在这两种之外再留一个季度抽查从目录里挑三到五份最近核对日期最久的记录逐条看状态是否仍准确。抽查的作用不是覆盖面而是防止某几份记录长期无人过问——过期的已接受比没有记录更容易误导人。八、动手练习与小结练习为一次真实决策补一份记录选一件大家都同意但说不清为什么的设计按下面四步写一份记录并把它的状态跑通一遍。第一步找出它的替代方案。如果找不到替代方案说明它更可能是约束而不是决策——换一个题目。找到替代方案之后把两个方案的关键差异写下来这就是背景和考虑过的替代方案两段的原料。第二步写背景与后果。背景写三条具体约束要求、限制、外部条件后果分正负两栏各写一到三条。写代价时故意往保守方向多写一点宁可写两条真实的代价也不要写基本没有代价——后者几乎总是意味着你没有认真想。第三步写修改条件并在仓库里提交。三条左右每条都要能被观察监控指标、外部变更、业务动作。和一次真实的代码改动一起提交在 PR 描述里引用它。第四步做一次半年后演练。请一位没有参与这件事的同事回答两个问题这条决策当初为什么这样定什么情况下应该重新考虑他答得上来说明记录是可用的答不上来回去修背景和修改条件。演练的成本很低却是检验记录质量的唯一实际方式。产出物是一份一页以内的决策记录以及一次演练反馈。把它和前面几篇的产物串起来任务单写这次做什么材料包写给哪些材料决策记录写为什么这样做。小结这一篇的核心是四件事。第一记录的重点在为什么代码已经记录了是什么没有为什么的团队会反复讨论同一件事甚至错误地推翻正确的决定。第二决策要跟代码走放进仓库、和代码一起评审、在 PR 里引用、能被检索包括被代理检索。第三一份记录一页就够六段结构——编号与状态、背景、决策、后果、替代方案、修改条件其中修改条件最重要它把决策从永远正确降级为在当前条件下正确。第四记录要维护状态而不是删除被替代、废弃都写清楚保留时间线。两个判断标准可以随身带着这条决策如果半年后有人质疑我需要花几分钟解释吗需要就写。这四条修改条件里有没有能被监控或者被观察到的事件没有就重写修改条件。两条都能通过这份记录就值得占用仓库里的一页。和前后篇的关系前几篇一直在处理规则——怎么写、怎么改、在哪一层落地这一篇处理的是规则背后的理由。理由和规则的关系是规则告诉你现在怎么做理由告诉你什么时候可以不一样。下一篇从规则转到工具当项目要挑选第一个自动化套件时怎么判断哪个工具最值得先装——这也是从知识回到工程的过渡。
返回列表