跳过正文

决策文档实用指南

· loading · loading ·
仁才德
作者
仁才德
居住在韩国首尔的领导者和软件工程师

每个团队都在做决策,而大多数团队会忘记当初为什么这么决定。半年后有人问“我们为什么选了 PostgreSQL 而不是 MongoDB?”,没有人记得当时的约束条件,于是同一场争论从头再来一遍——而且到场的人通常比上次还少。

决策文档就是解这个问题的。叫 ADR、RFC 还是决策日志都无所谓,关键是在做选择的那一刻,把背景、备选方案和推理写下来。今年开始带开发团队之后,我对它的价值体会得更深了:它决定了团队拥有的是组织记忆,还是组织传说。


为什么值得花这个功夫
#

最直接的回报是不用把同一场架吵两遍。“API 版本策略不是已经定了吗?”只要决定了什么、为什么决定有清楚的记录,尘埃落定的问题就能保持落定,团队可以把力气花在执行上,而不是原路折返。

更安静的回报要过一段时间才显现。项目一跑就是几个月甚至几年,人来人往,最后留下的只有代码——代码能告诉你决定了“什么”,却永远不会告诉你“为什么”。决策文档保存的正是推理本身:当时存在的约束、被否掉的备选方案、拍板的人是谁。新人得到的是上下文而不是传说;跨团队的决策因为所有人都能看到权衡的因素而更容易获得真正的认同;决策者和参与者都写着名字,后续落实也就有了明确的主人。


什么时候写
#

不是每个决策都配得上一份文档。我会在决策属于以下情况时动笔:

  • 回头代价高的 — 技术栈选择(框架、数据库、云服务商)、架构模式(微服务还是单体、事件驱动还是请求-响应)、第三方供应商选型、安全与合规方案
  • 影响长期存在的 — 外部系统依赖的 API 契约、跨多个服务的数据模型、对既定模式的破坏性变更
  • 需要跨团队的 — 需要多个团队或非技术相关方认同的事项、资源分配和优先级取舍
  • Spike 的结论 — 限时调研结束后,趁发现和决定还没蒸发,赶紧落成文档

反过来,容易撤销的决定、团队内部的实现细节、对自己范围之外几乎没有影响的选择,就别走形式了。没人需要的文档只是作业。


生命周期
#

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│    草稿     │ ──► │    评审     │ ──► │    批准     │ ──► │    实施     │
│    提议     │     │   反馈      │     │    接受     │     │    完成     │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
                    ┌─────────────┐
                    │   否决/     │
                    │   取代      │
                    └─────────────┘

作者起草问题和候选方案,相关方进行评审——整套流程的价值主要就在评审这一步,因为假设被挑战、盲点被暴露都发生在这里。然后由决策者(通常是技术负责人、架构师或产品负责人)拍板,团队实施;实施中如果现实逼着改了方案,文档也要跟着更新。有的文档死在评审里,有的日后随情况变化被取代——两种都要清楚标记,一份过期却还挂着“已批准”的文档,是给下一个发现它的人挖的坑。


什么样的文档才值得读
#

从问题开始,而不是从你心仪的方案开始。我们在解决什么?为什么是现在?什么都不做会怎样?如果大家对问题本身都没有共识,讨论方案就是演戏。

然后给出真正的备选项——哪怕你已有强烈倾向,也至少写两三个。每个选项配上简短描述、优缺点、粗略的工作量估算(T 恤尺码就够用:S/M/L/XL),以及风险和应对办法。把落选方案写下来,未来的读者才能明白胜者为什么胜出。

把取舍大声说出来。“我们选择上市速度,放弃架构纯粹性。”“我们接受运维复杂度上升,换取更好的扩展性。”“我们把开发者体验放在原始性能之前。”这样的句子对未来读者的帮助,超过几页纸的分析。

让讨论落在具体的东西上:展示方案实现样子的代码片段、系统交互图、指向 Spike 概念验证的链接。抽象的讨论只会产出抽象的决定。

最后,给它一个期限:什么时候必须定下来,谁在什么时间前要给意见,拖着不决会卡住什么。没有期限的决策会永远飘着。


示例:架构决策记录(ADR)
#

下面是一个实际的决策文档示例:

# ADR-001: API Authentication Strategy

**Status:** Accepted
**Date:** 2024-11-15
**Decision Maker:** Sarah Chen (Platform Architect)
**Contributors:** Backend Team, Security Team, Mobile Team

## Context

Our public API currently uses API keys for authentication. As we expand to
support third-party integrations and mobile apps, we need a more robust
authentication mechanism that supports:
- Token expiration and refresh
- Scoped permissions
- User-level authentication (not just service-level)

## Decision

We will implement OAuth 2.0 with JWT tokens for API authentication.

## Options Considered

### Option 1: OAuth 2.0 with JWT (Recommended)
**Description:** Industry-standard protocol with self-contained tokens

| Pros | Cons |
|------|------|
| Industry standard, well-documented | More complex initial implementation |
| Self-contained tokens reduce database lookups | Tokens cannot be revoked instantly |
| Broad library support | Requires refresh token management |

**Effort:** Medium (2-3 sprints)

### Option 2: Session-based Authentication
**Description:** Traditional server-side sessions with cookies

| Pros | Cons |
|------|------|
| Simple to implement | Not suitable for mobile apps |
| Easy to revoke sessions | Requires sticky sessions or shared session store |
| Familiar to most developers | Doesn't scale as well |

**Effort:** Small (1 sprint)

### Option 3: Custom Token System
**Description:** Build our own token-based authentication

| Pros | Cons |
|------|------|
| Fully customizable | Reinventing the wheel |
| No external dependencies | Security risks from custom implementation |

**Effort:** Large (4+ sprints)

## Consequences

- Mobile team can implement standard OAuth flows
- We'll need to set up a token refresh mechanism
- API documentation will need updates for OAuth flows
- Existing API key users will need a migration path (6-month deprecation)

## References

- [OAuth 2.0 RFC 6749](https://tools.ietf.org/html/rfc6749)
- Internal spike document: [Authentication Options Spike](/spikes/auth-spike-2024)
- Security team review: SEC-2024-042

一份可以直接拿走的模板
#

把它当起点,按团队需要往下删:

属性详情
决策/议题名称[清晰、描述性的标题]
状态[草稿 / 评审中 / 已批准 / 已否决 / 已取代]
影响度[高 / 中 / 低]
负责人[推动决策的人]
决策者[拥有最终决定权的人]
截止日期[做出决策的截止时间]

问题描述
#

我们要解决什么问题?为什么重要?不采取行动的代价是什么?

背景
#

提供上下文、历史和相关细节。链接到相关文档、过去的决策或Spike结果。

约束条件
#

所有方案必须遵守哪些限制或要求?

  • 预算约束
  • 时间线要求
  • 技术约束(现有系统、技能储备)
  • 合规或安全要求

考虑的方案
#

选项描述优点缺点工作量风险
选项 1描述优点A,优点B缺点A,缺点BS/M/L/XL低/中/高
选项 2描述优点A,优点B缺点A,缺点BS/M/L/XL低/中/高
选项 3描述优点A,优点B缺点A,缺点BS/M/L/XL低/中/高

建议
#

说明推荐的选项,并总结在约束和取舍条件下为什么它是最佳选择。

决定
#

做出决定后记录最终决策。如果与建议不同,说明原因。

后果
#

这个决策有什么影响?需要哪些后续工作?

行动项
#

行动负责人截止日期
行动 1姓名日期
行动 2姓名日期

参考资料
#

相关文档、外部资源、Spike结果或过去决策的链接。


让习惯扎根
#

文档放在固定的地方——仓库里的专用文件夹也好,Wiki 空间也好——找不到的决策等于不存在。保持轻量:一份要写好几天的文档根本不会被写出来。在代码注释、提交信息和 PR 里引用决策文档,让“为什么”一直贴着“是什么”。决策被取代时别删旧文档,标记为已取代并链接到新决策,历史本身就是价值。另外,在回顾会上时不时翻翻过去的决策:哪些经受住了考验,哪些换成今天你会做得不一样。

失败的方式也同样好预测。分析瘫痪:定个期限,用手头的信息做决定,大多数决策以后都能再议。橡皮图章式评审从来提不出真正的批评,只加流程不加价值。写了却从未落地的孤儿文档,比没有文档更糟。细节过多也是病:文档管“为什么”和“是什么”,不管“怎么做”,实现细节属于技术规格书。还有,当有人不同意时,把反对意见也写进去——决定也许照旧,但那条异议恰恰是一年后某个人最需要的上下文。

从你的下一个重要决策开始吧。写得糙一点没关系,流程边用边磨。每一场因为有人直接读了 ADR 而没开成的“我们当初为什么选 X”会议,都是还给团队的时间。


延伸阅读
#

本站相关文章:

外部资源: