Principle 01
设计文档:写给六个月后的自己
Design Docs & RFCs — Writing as Thinking, Not Archiving
RFC · 设计文档
一句话原则 + 名家原话
设计文档的第一读者不是 reviewer,是"半年后的你自己"。它替你保存当时的判断、否决过的选项、那个"为什么不"——而不是表演当时的聪明。
"The single most important point of a design doc is to make the author reason about the design at the right level of abstraction."
— Malte Ubl《Design Docs at Google》(industrialempathy.com, 2020)
中译:设计文档最重要的一点,是逼作者在恰当的抽象层级上对设计进行推理——不是把已经定好的事归档下来。
原理解读
工程师常误以为"文档 = 把已知决定记下来"。错。文档的真正价值在写的过程:把模糊感落成句子,半数缺陷会在打字时自己露出来。这是 "writing is thinking" 的工程版。
而半年后回看的你(或新人)问的不是"这是什么"——是"当初为什么不选 B"。所以 Alternatives Considered 几乎是整份文档最有价值的一节:它替你保存"已被否决"的方案与理由,避免下一个 engineer 又走一遍坑。
Context & Goals
问题在哪、解到什么程度算赢
Non-goals
明确不解什么——挡 scope creep 的盾
Alternatives Considered
至少 3 个 · 每个含"为什么不"——本节最有长尾价值
Risks & Open Questions
已知风险 + 还没想清楚的事(诚实标出)
Rollout & Rollback
怎么 ship、出事怎么撤
六节骨架。不全等于不写完,全了不等于写好——但缺哪节,半年后那节就是"诡异巧合"的产地。
修改示范
We will use Kafka for the event pipeline because it is scalable and reliable.(典型空话——"scalable / reliable" 对所有候选都成立,等于没说)
Chose Kafka over Pulsar after benchmarking both at our 10K msg/s peak (3 days each). Pulsar's broker-bookie split adds two ops surfaces we don't have headcount for. Tradeoff accepted: weaker geo-replication, fine through Q3 because all consumers are us-east. Revisit when EU goes live.
本设计采用微服务架构,提升系统可扩展性。(同上空话;"提升可扩展性"什么都没说)
在"单体 + 模块化包"与"三个微服务"两案之间,选后者。关键依据:付费链路与免费链路的发布节奏过去 8 周已差 3 倍(数据:deploy 频次 12 vs 4),共享代码库的合并冲突是主要瓶颈。已否决的第三案"事件驱动重写"——团队近两年无人做过 event sourcing,引入新范式的人力成本超过收益。
适用场景 + 常见错误
- ✓ 任何 ≥ 2 周 / ≥ 1 人月 的工程决策 · 跨团队接口变更 · 架构基线迁移
- ✗ 不适用:单文件 refactor · 产品 micro-copy 调整——用 PR 描述即可
- 错误一:写完才开始——文档应在决定之前写,不是事后归档
- 错误二:Alternatives Considered 只写"考虑过但没选",没写"为什么没选"——丢了全部长尾价值
- 错误三:用伪码 / class diagram 替代设计——读者读不到 why、读不到 tradeoff
- 错误四:没有 Non-goals——reviewer 持续问"那 X 怎么办",本来不在 scope
关键参考
Malte Ubl《Design Docs at Google》industrialempathy.com 2020 · Will Larson《An Elegant Puzzle》Ch. 4 — 决策与备忘录在工程组织中的杠杆 · Gergely Orosz"How Big Tech Runs Tech Projects" — RFC / TDD 实操对比
本周习作 + 思考题
习作:找你手上的下一份 RFC。把 "Implementation Details" 砍掉一半,把 "Alternatives Considered" 扩到原来的两倍。让一个跨团队同事先读——他若问"为什么不 X",那段 X 就该加进 Alternatives。
思考:当 AI 协作 RFC 的比例升高,"reason about the design"这件事谁在做?AI 起草的稿是否反而让作者懒得思考?怎样的人机分工能保留 "writing is thinking" 的效用?
Principle 02
API 文档:四种文档不能写在一页里
API Docs & Diátaxis — Four Modes, Four Pages
API · Diátaxis
一句话原则 + 名家原话
一个页面不能同时教初学者、查 reference、写攻略、讲原理——把"教程 / 任务指南 / 参考 / 解释"分开,读者来了各取所需。
"Documentation needs to include and be structured around its four different functions: tutorials, how-to guides, technical reference and explanation. Each of them requires a distinct mode of writing."
— Daniele Procida《Diátaxis》(diataxis.fr, PyCon AU 2017)
中译:文档须包含并围绕四种功能展开——教程、任务指南、技术参考、原理解释。四者要求四种截然不同的写法。
原理解读
不分会怎样?新手读 reference 一脸懵;老手翻教程嫌啰嗦;运维查任务指南却读到"为什么这么设计"——所有人都恼。Diátaxis 把文档放在两个轴上:纵向"学习 vs 工作",横向"实践 vs 认知"。四象限各司其职。
实践 Action
认知 Cognition
学
习
教程 Tutorials
learning-oriented
"跟我做一遍"——一条 happy path,10 分钟跑出 Hello World。不解释为什么。
解释 Explanation
understanding-oriented
"为什么是这设计"——背景、tradeoff、历史。给已上手、开始好奇的人。
工
作
任务指南 How-to
problem-oriented
"如何处理 webhook 重试""如何在 sandbox 测退款"——已知问题的食谱。
参考 Reference
information-oriented
全 API 表 · 字段 · 类型 · 错误码。机器可生成的那种。冷峻、完整、无故事。
四象限。Stripe 的 API 文档被公认金标准,关键就是把四类严格分开 + 提供跨锚导航。
修改示范
单页 "Getting Started"——300 行混了"安装 / 快速跑通 / 全 API 表 / 我们为什么这么设计"。结果:新手卡在 API 表,老手在故事里找字段。
拆四页:① Tutorial("10 分钟跑出第一笔 charge",单一 happy path)② How-to("如何处理 webhook 重试""如何在 sandbox 测退款")③ Reference(全 API + 字段,自动生成)④ Explanation("为什么 idempotency key 是这设计")。首页只是分流入口。
A single markdown mixing function signature + usage demo + caveats + design philosophy. Readers can't tell where one ends and the next begins.
Signature & demo go to Reference. Caveats go to How-to ("Handling rate-limits"). Design philosophy goes to Explanation. Reference page links out to both — readers in scan mode stay in Reference; readers in "why" mode click through.
适用场景 + 常见错误
- ✓ 公开 API · SDK · 内部平台(开发者向)· CLI 工具
- ✓ 文档站点重构——Diátaxis 是顶层 IA(信息架构),不是样式选择
- ✗ 不适用:单文件库(一页 README 足够)· 一次性脚本
- 错误一:把四类全塞进 README——README 是"扉页",不是"全集"
- 错误二:写 tutorial 像 reference——堆 API 表不讲 happy path
- 错误三:写 reference 像 tutorial——堆故事不堆字段
- 错误四:explanation 摆首页——大多数读者来时要 how,不要 why;explanation 是给"已上手、开始好奇"的人
关键参考
Daniele Procida《Diátaxis: A systematic framework for technical documentation》diataxis.fr · Stripe API Docs docs.stripe.com — Diátaxis 教科书级实践 · Google《Technical Writing Courses》developers.google.com/tech-writing — 免费两阶段课程
本周习作 + 思考题
习作:找一份你自己写过的 API 文档(不限规模),用 Diátaxis 四象限给每段标类。任一类为零,问自己:是真不需要,还是默认漏了?教程页 / explanation 页缺,常常就是"看起来完整、用起来卡"的原因。
思考:当 LLM 成为大部分 API 的"中介读者"(开发者通过 ChatGPT 调你的 API),文档该为人写还是为 LLM 写?哪几类 LLM 反而更不擅长读(tutorials?explanation?)?
Principle 03
ADR:替决定盖时间戳
Architecture Decision Records — Immutable Memos for the Future
ADR · 决策记录
一句话原则 + 名家原话
决定 = 上下文 + 选择 + 后果。三者缺一,半年后回看就成"诡异巧合"。ADR 替决定盖时间戳——一旦写下不再改,要变只能写新的 ADR 标 "Supersedes ADR-042"。
"We will keep a collection of records for 'architecturally significant' decisions: those that affect the structure, non-functional characteristics, dependencies, interfaces, or construction techniques."
— Michael Nygard《Documenting Architecture Decisions》(Cognitect blog, 2011)
中译:我们要为所有"架构上重要"的决定保留记录——任何会影响系统结构、非功能特性、依赖、接口或构造方法的决定。
原理解读
设计文档讲 design,ADR 讲 decision——两者不同。设计文档可讨论几个方案,最后由 ADR 落锤"我们选了 A"。
ADR 的关键不在格式(五字段而已),在 immutable 的态度:一旦写下不再改。这是 git commit 思维,不是 wiki 思维。若改了 mind,写一份新的 ADR 标 "Supersedes ADR-042",原文保留——历史不被改写、学习才能传承。
Title
ADR-042: Migrate to Postgres 15
Status
Accepted · 2026-05-10 · authors: bc, jh · superseded by ADR-067 (2027-03)
Context
PG12 reaches EOL 2024-11. Security patches end. PG16 released 2023-09 (8 months old, low adoption). PG15 mature, GA > 2 years. Our shop is conservative on database majors.
Decision
Migrate prod & replicas to PG15 (not 16). 2-week window Q3 weekend.
Consequences
+ JSONB performance gain · + EOL exit before EU regulatory review · − must rewrite 12 JSONpath queries to PG15-compatible form · − loses access to PG16's improved logical replication; revisit in 12 months.
修改示范
PR description: "Switch to Postgres 15"(→ 18 个月后新人 reading the repo:"为什么 PG15 不是 PG16?"——无人能答)
ADR-042 上述五字段。文档活在 docs/adr/0042-postgres-15.md。永不被改写。18 个月后新人翻 ADR 即可:决定与上下文一目了然——他知道"当时不是因为蠢,是因为风险偏好"。
Wiki 页面"数据库选择"——半年内被五个人编辑过 12 次,谁也说不清当时为什么选 PG。每次新人入职都开同一场争论会。
同一决定迁到 ADR-042 + immutable git history。"为什么选 PG"问题永久关闭:链接到 ADR,5 分钟读完。新人若不同意,写 ADR-067 提议替代——附其反方论据。组织进步走在 ADR 链条上,历史不丢。
适用场景 + 常见错误
- ✓ 框架 / 数据库 / 协议 / 区域基础设施选择 · 公司级 build vs buy · 关键依赖升降级
- ✓ 任何"未来人会问'为什么是这个'"的决定
- ✗ 不适用:日常实现选择(哪个第三方包、变量命名)——太频繁 ADR 反成噪音
- 错误一:把 ADR 当 wiki——可改、被改、改完没人记原版。ADR 是 immutable
- 错误二:只写 Decision、不写 Context——读者无法判断"现在还成不成立"
- 错误三:不写 Consequences——下一个人不知作者已认知的 tradeoff,重新踩坑
- 错误四:写得太晚——决定执行后再补 ADR 等于"考完试再写答案",丢了思辨的痕迹
关键参考
Michael Nygard《Documenting Architecture Decisions》Cognitect blog 2011 — 五字段原文 · ThoughtWorks《Technology Radar》— "Lightweight Architecture Decision Records" (Adopt, 2018) · adr.github.io — 模板与社区集合
本周习作 + 思考题
习作:翻你过去半年最重要的一个技术决定(无论当时是否有 ADR),用 5 字段补写一份。重点是 Context——回到当时,你不知道现在知道的什么?把这个差异写出来。
思考:ADR 的 immutability 与组织学习似乎矛盾——人会进步、知识会更新。怎么设计 ADR 与"后续修正"的关系,让历史不被改写、但学习能传承?
Principle 04
代码注释:WHY 由注释,WHAT 由命名
Code Comments — Why & Why-not, Not What
Comments · 哲学
一句话原则 + 名家原话
注释解释 WHY 与 WHY-NOT;命名解释 WHAT。两者重叠 → 噪音。当你想加一行解释 what 时,先问:能不能改个变量名或抽个函数?
"The most important reason to write comments is abstractions: comments can describe things that can't be inferred from the code. Without comments, you can't hide complexity."
— John Ousterhout《A Philosophy of Software Design》Ch. 12(Stanford, 2018)
中译:写注释最重要的理由是抽象——它能描述代码本身推不出来的东西。没有注释,复杂度无法被隐藏。配合 Robert Martin《Clean Code》Ch. 4:"Don't comment bad code—rewrite it."(不要为烂代码加注释——重写它)。
原理解读
三层分工:
| 维度 | 由谁承担 | 例 |
| WHAT 做什么 | 命名 | retryBudget · archiveUsersBefore() |
| HOW 怎么做 | 代码本身 | 函数体 |
| WHY 为什么 | 注释 | # 3 因为上游 SLA P95 = 800ms |
| WHY-NOT | 注释 | # 不用 async:worker 共享 db conn |
| 不变量 | 注释 | # list must stay sorted — bisect downstream |
| 契约 | docstring | "Returns timestamp in UTC ms; raises TimeoutError after 5s" |
注释还有一个被忽视的作用:标"未来的不变量"——如 # this list must stay sorted — binary search downstream。这是文档化的约束,告诉下个 engineer "改它前先看后果"。
修改示范
注释复述代码——零信息:
# increment i
i += 1
注释解释"3"这个数字的来源:
# retry budget is 3 — beyond this upstream
# (payments-svc) gives up, so we should too
i += 1
注释像残片,"legacy" 含义模糊;魔法数字硬编码:
# fix for legacy users created before 2018
if user.created_at < datetime(2018, 1, 1):
user.role = "legacy"
提取常量做 WHAT,注释指向 ADR:
# Pre-cutoff users predate the role-schema migration
# in ADR-019; default to LEGACY so they don't get
# write-locked by new RBAC checks.
if user.created_at < LEGACY_USER_CUTOFF:
user.role = ROLE_LEGACY
注释做命名的工作:
// loop through users
for (const u of users) { ... }
删掉注释——for (const u of users) 已经自明。这就是 Martin "Don't comment bad code—rewrite it" 的简化版:"Don't comment obvious code—delete the comment."
适用场景 + 常见错误
- ✓ 任何非显而易见的"为什么是这数""为什么不那样写""未来需小心改"
- ✓ 公共 API / SDK / 跨团队接口——契约必须 docstring 化(前置 / 后置条件、异常、单位)
- ✗ 不适用:复述代码 · 装饰性 banner · 版本号(git 已记录)· 姓名版权(在 LICENSE)
- 错误一:注释做 WHAT 的工作——改名 / 抽函数代替
- 错误二:注释撒谎——代码改了注释没改。过期注释比没有注释更糟,因为它主动误导
- 错误三:JSDoc / docstring 当装饰——只写类型不写语义,读者还是不知单位、不知失败模式
- 错误四:在烂逻辑前加一段诗——若需要一整段解释,先看能不能把代码改清晰
关键参考
John Ousterhout《A Philosophy of Software Design》(2018, 2nd 2021) Ch. 12–15 — 注释与命名的核心读物 · Robert C. Martin《Clean Code》Ch. 4 — 注释的 17 戒 · Steve McConnell《Code Complete》Ch. 32 — 自文档化代码原则
本周习作 + 思考题
习作:在自己最近的一个 PR 里,逐行注释做"三判定":(a) 复述代码 → 删;(b) 解释 WHY → 留;(c) 命名能替代 → 改名再删。Bonus:找一个三个月前的老注释,看代码与注释是否还吻合;不吻合就更新或删——注释卫生最 underrated 的练习。
思考:当 AI 能即时为代码生成"它在做什么"的注释,WHAT 类注释的边际成本趋零。这是好事还是坏事?AI 能否生成 WHY 类注释——还是 WHY 永远是人类作者保留的东西?