文档写得很全:标题清晰、目录完整、步骤带编号。读者照着做完,还是不知道该选哪个方案、哪一步出问题该找谁。原因通常不在文笔,而在两件事没定过——写给谁,以及承诺什么。
把文档当契约看,问题立刻具体:写下来的部分会被引用、被照着执行、在争议时被翻出来当依据;没写的部分不是"遗漏",而是明确交给读者自行判断。所以第一个决策不是"写多全",而是"对谁承诺到哪一层"。
1. 三类读者,要的东西完全不同
一份文档同时满足三类读者是做不到的。他们关心的问题、要的产物、能容忍的篇幅都不同:
| 读者 | 关心的问题 | 要的产物 | 合理篇幅 | 读完的动作 |
|---|---|---|---|---|
| 决策者 | 选 A 还是 B,代价是什么 | 判据、代价表、失效条件 | 一屏到两屏 | 拍板或退回 |
| 执行者 | 这一步敲什么 | 命令、参数、预期输出 | 可跳读的步骤 | 完成操作 |
| 未来的排查者 | 当时为什么这么定 | 约束、排除的选项、时间点 | 可检索的索引 | 定位影响面 |
三类读者混在一份文档里,结果是决策者嫌长、执行者嫌绕、排查者找不到。可行做法是一份主文档只承诺一类读者,其余两类靠链接跳转。
2. "写给谁"决定篇幅、详略与判据
判据不是"这句话重不重要",而是"这类读者用它做决定吗"。同一个参数,对执行者是必写项,对决策者是噪音:
| 内容 | 决策者 | 执行者 | 排查者 |
|---|---|---|---|
| 结论与代价对比 | 必需 | 可跳过 | 需要 |
| 精确命令与参数 | 不要 | 必需 | 需要 |
| 当时的约束条件 | 需要 | 不要 | 必需 |
| 内部实现细节 | 不要 | 局部需要 | 视故障而定 |
| 变更历史 | 不要 | 不要 | 必需 |
在文档头部显式声明读者,是最便宜的一道约束——它让"要不要写这段"变成可判定的问题:
// 文档头部元信息:读者与承诺边界一起声明
audience: [决策者] // 只承诺这一类读者
not_for: [执行者, 终端用户]
promise: 判据与代价,不含操作步骤
owner: [NAME]
review_by: 2026-12-31 // 到期未复核即视为待验证
supersedes: [OLD_DOC_ID]
3. 承诺性质:写了就是承诺,没写的就不是
文档的承诺强度不由语气决定,而由"读者能否据此追责"决定:
| 写法 | 实际承诺 | 读者会怎么用 |
|---|---|---|
| "建议使用 X" | 弱:可选建议 | 参考,被推翻不算违约 |
| "使用 X" | 中:默认路径 | 照做,出问题回来找文档 |
| "必须 X,否则 Y 失效" | 强:硬约束 | 当检查项,写进评审清单 |
| 表格里列出的字段与取值 | 契约级 | 当接口定义,写进代码校验 |
常见失衡是处处用"建议":作者以为这样就不必担责,读者却读不出哪条必须遵守,于是要么全部照做(成本失控),要么全部忽略(文档失效)。
4. 文档类型决定承诺强度
文档类型不是命名习惯,它对应的承诺强度与变更成本差一个量级:
| 类型 | 承诺强度 | 允许的模糊度 | 变更成本 | 失效代价 |
|---|---|---|---|---|
| 概览 | 最低 | 高,可不精确 | 极低 | 小 |
| 指南 | 中 | 低,步骤须可执行 | 中 | 中,读者卡住 |
| 参考 | 高 | 极低,取值须准确 | 高,随实现同步 | 高,被当契约 |
| 决策记录 | 高(针对"为什么") | 低 | 低,写完即冻结 | 中,重复讨论 |
最容易出错的是把参考写成指南的语气:读者把示例值当默认值用,实现一改,文档就成了错误来源。
5. 承诺强度要写在句子里,不写在语气里
同一件事的三种写法,读者可追责的程度完全不同:
// 弱:可选建议,被推翻不算违约
建议把 TIMEOUT_MS 设为 300
// 中:默认路径
TIMEOUT_MS 使用 300
// 强:硬约束,违反即失效
TIMEOUT_MS 必须为 300,否则重试会叠加为一次写入
团队里"必须"用得多不多,不是风格问题,而是承诺预算问题:每多一条"必须",就多一条需要被验证、被同步、被解释的约束。
6. 维护是最大成本,判据是"会被第二个人读第三遍吗"
写一次的成本是固定的,维护成本按被引用次数放大。判断一篇是否值得继续维护,看三个信号即可:
// 文档维护体检:三类信号一起看
allocated=$(grep -r "DOC_ID" docs/ | wc -l) // 被引用次数
stale=$(find docs -name '*.md' -mtime +180 | wc -l) // 半年未改动
orphan=$(grep -rL "DOC_ID" CODE_PATHS | wc -l) // 无代码指向
| 信号 | 阈值倾向 | 处置 |
|---|---|---|
| 半年内无任何引用 | 典型量级:多数文档落在这里 | 降级为个人笔记或删除 |
| 被同一人反复读 | 说明是私人备忘录 | 收敛进注释或脚本 |
| 被两人以上、跨版本读第三遍 | 真正的契约文档 | 投入同步机制 |
7. 腐化有两种,检测手段不同
| 腐化类型 | 表现 | 检测手段 | 修复方式 |
|---|---|---|---|
| 内容过期 | 参数名、默认值、截图与实际不符 | 抽样跑一遍文档里的命令 | 局部改写,保留结构 |
| 结构失效 | 章节顺序不再对应阅读路径,检索不到 | 让新人按目录找一件事,计时 | 重排目录,拆分子文档 |
内容过期靠评审发现,结构失效只能靠新人的真实检索过程发现——这也是为什么文档体检要定期换一个人来做。两类腐化可以放进同一次例行检查:
// 腐化体检:内容、结构、时效三条线分开跑
grep -rn "PARAM_NAME" docs/ | grep -v CODE_PATHS // 文档提到、代码已无
diff <(sed -n 's/^## //p' DOC.md) ORDER.txt // 目录与阅读顺序不一致
grep -rLn "review_by" docs/ | head // 缺时效字段的文档
三条线都要有 owner:没有 owner 的检查,第一次跑完后就不再有人跑第二次。
8. 当规格用:必须可被验证
文档一旦被当接口契约,它的承诺必须能被机器检查,否则只能靠人记:
// 文档里的契约片段,可被 CI 直接断言
{
"endpoint": "ENDPOINT",
"required": ["PARAM_A", "PARAM_B"],
"enum": { "status": ["PENDING", "DONE"] },
"idempotent": true
}
同一份字段定义应当只有一处权威来源,文档由它生成或对它做断言;出现两处并列,就等于约定了一处会先过期。按当前 CI 版本核对生成与断言插件的可用形态。
9. "已废弃"标注比删除更好,但要有下线日
直接删除会让引用它的代码与讨论失去落点。更稳的做法是标注 + 保留 + 定时下线:
// 文档头部加废弃块,三者缺一不可
deprecated: true
replaced_by: [NEW_DOC_ID]
sunset: 2027-03-31 // 到期删除;引用数须先降到 0
| 状态 | 允许的读者动作 | 触发动作 |
|---|---|---|
| 现行 | 引用、照做 | 无 |
| 待验证 | 引用但需核对 | 到期未复核 → 降级 |
| 已废弃(有替代) | 只读,不照做 | 引用数归零 → 删除 |
| 已废弃(无替代) | 只读,作为历史证据 | 不删除,标注归档 |
10. 位置决定可见性,可见性决定它是否被读
| 存放位置 | 典型可见性 | 适合的文档 | 主要风险 |
|---|---|---|---|
| 代码库内(与代码同仓) | 改动时必然被看到 | 参考、决策记录 | 与代码一起被删 |
| 内部站 / wiki | 需主动搜索 | 概览、跨团队指南 | 检索不到即不存在 |
| 聊天记录 / 工单 | 只有当时在场的人知道 | 一次性沟通 | 一周后无法检索 |
排序规则很直接:会被代码消费的放代码库,会被跨团队查阅的放内部站,只在对话中有意义的留在对话里。放错位置的后果不是难看,而是"写过了但没人能找到"。
11. 成本分摊:谁写、谁审、谁删
| 角色 | 动作 | 时机 |
|---|---|---|
| 变更提出者 | 写:改了什么、为什么、影响谁 | 设计阶段 |
| 相邻模块负责人 | 审:判据是否成立、边界是否漏 | 评审前 |
| 文档 owner | 删:到期、无引用、被替代 | 每季度一次 |
| 值班/排查者 | 回填:现场与实际不符之处 | 事故后 |
无人负责"删",是文档库膨胀的唯一根因;无人负责"审",是承诺强度失控的根因。
12. 度量:看引用与时效,不看篇数
| 指标 | 口径 | 异常信号 |
|---|---|---|
| 被引用次数 | 代码/评论/工单里出现的次数 | 长期为 0 |
| 过期文档数 | 超过 review_by 未复核 | 单调上升 |
| 平均阅读时长 | 站内停留或翻页深度 | 极短,说明没读到结论 |
| 修复延迟 | 发现不符到改完的时间 | 超过一个迭代 |
13. 排错:五种"写了等于没写"
| 现象 | 原因 | 处理 |
|---|---|---|
| 读者读完还问"我该选哪个" | 只有描述没有判据 | 补代价表与失效条件 |
| 文档被当成规范,实现却相反 | 参考写得像指南 | 明确类型与承诺强度 |
| 反复被问同一个问题 | 位置错误,检索不到 | 移到代码库或入口页 |
| 一年后发现内容全错 | 无复核期、无 owner | 加 review_by 与季度清理 |
| 删了文档后代码失据 | 直接删除、无替代 | 先标注废弃与替代链接 |
14. 边界:哪些情况正式文档化是浪费
| 情形 | 更合适的形式 | 理由 |
|---|---|---|
| 一次性沟通(约时间、同步进度) | 对话本身 | 无第二读者,无承诺 |
| 内容一周内必然失效 | 代码注释或脚本内部说明 | 文档寿命短于维护成本 |
| 读者只有自己 | 个人笔记 | 无需承担对外承诺 |
| 已经由代码强制约束 | 类型/校验/schema | 机器执行比文字更可靠 |
| 探索期的临时结论 | 决策记录的草稿区 | 结论未定,写死反成负担 |
判断只需一句:这份东西会被第二个人读第三遍吗? 会,就按契约写;不会,就找更轻的载体。
15. 自测
- 一份文档同时承诺决策者与执行者,最先失控的是哪一项?
- "建议使用 X"与"使用 X"在争议时有什么区别?
- 哪两类腐化只能靠换人阅读才能发现?
- 文档要当接口契约用,缺哪一步它就只能靠人记?
- 将已废弃文档直接删除,会造成什么具体损失?
- 判断"该不该正式文档化",只用哪一句提问?