写给读者的单向沟通
读者是没有你当前上下文的人:后来的队友、接手的维护者、半年后的你。一个优秀软件工程师大约一半功力在写代码,另一半在与他人沟通——本 skill 管「写给别人读」的那一半。这类写作只回答一个主线问题——为什么(why),而不是做了什么(what)。做了什么看代码和 diff 就懂;为什么才是来之不易、最容易随时间流失的知识。这条主线叫 why-not-what:code-review 与 contributing-upstream 在交接点都引用这里的契约。
操作契约
- 先问,不猜。 写 commit、注释或 README 前,若不知道改动背后的动机、取舍与遗留问题,先向用户补齐上下文。commit 场景必问四件事:① 什么问题或约束迫使这次改动?② 考虑过哪些备选方案、为何选这个?③ 取舍与影响是什么?④ 有哪些读者会意外的点?用户答不上来就标注「作者未说明」,绝不编造动机。
- 详略与复杂度匹配。 一行错别字只需要主题行;修一个排查数小时的竞态条件,值得用段落解释问题与解法。显而易见的段落可以省略(改一个 off-by-one,就没有「备选方案」可写);但再平凡的改动,也别只剩一句「fix foo」——零信息量的提交等于没写。
- 克制,尊重读者。 读者面对太多文字时,一个字都不会读;写作前自问:如果我是读者,我会读吗?解释为什么,相信读者会为自己的情境推出怎么做。用 LLM 生成时尤其要约束它——LLM 擅长批量产出文字,务必要求「精炼的总结,不是长文」。
- 提交拆得语义清晰(
git add -p):一个提交 = 一个可独立理解、独立评审的连贯改动。重构不与新功能混提,无关 bug 修复不塞进同一个提交。LLM 也可以帮你把大 diff 按语义切成多个提交,但切完要自己检查一遍。 - 复杂改动升级盘问。 背景盘根错节时,改用
/grilling逐轮深挖上下文,而不是让用户一次讲完。
注释:写代码本身表达不了的内容
好注释解释「为何这样做」,而不是「如何工作」——代码已经展示了过程。值得写的注释类型:
- TODO:留足上下文——还缺什么、为什么延期。写在代码里而不是只放 issue tracker,好处是后来者可以直接 grep 到。「TODO: optimize」毫无价值;「TODO: 这段 O(n²) 循环在 n<100 时没问题,规模放大后需要索引」可行。
- 参考资料:实现论文算法、借鉴外部代码或遵循文档规定行为时,给永久链接,并注明与参考实现的差异。
- 正确性说明:解释为什么不寻常的代码能产生正确结果。代码展示步骤,注释说明步骤为何奏效。
- 血泪教训:花了 30 分钟以上才调通、修复方式不明显——记下来。过去的你不知道需要这一步,未来读者也不会知道。
- 常数的理由:魔法数字也要解释。为什么是 1492?随手选的、测试得出的还是正确性所需?即便「随意选的」也是有用信息。
- 承重细节:正确性依赖某个看似无关的实现细节(如「必须是 BTreeSet,因为下面迭代顺序有要求」)——务必点出来。
- 「为什么不用」:刻意避开显而易见的做法时说明理由。典型场景:这里本该用标准库的 hash map,却用了别的结构——不写清楚,某位聪明工程师会把它「修」回标准库,然后重走你踩过的坑。
复述代码的注释是噪音,甚至误导读者——不写。
README:像漏斗一样组织
按顺序回答四个问题:它做什么?我为何要在乎?如何使用?如何安装? 顺序很重要——先展示用法、后讲安装,人们想先看到能得到什么,再决定投入安装步骤。顶部一句话描述(可加视觉示意),让人几秒内判断是否解决自己的问题;整体保持精炼、可扫读(skimmable),不要变成什么都往里塞的杂物袋。README 面向使用者;面向协作者的内容(bug 报告流程、PR 流程、测试方式、代码约定)放 CONTRIBUTING.md,不占 README。
提交信息:记录「为什么」的历史
提交信息构成代码库演进史。有人(包括你)跑 git blame 弄懂某处困惑改动时,它要能回答:什么问题迫使我们改动?考虑过哪些备选方案?取舍与影响是什么?有哪些可能令人意外的点?复杂改动采用「问题 → 解决方案 → 影响」结构;最后一部分尤其重要——记录某个取舍是刻意的,防止后人以为你忽略了问题。取舍常藏在非显而易见处:比如让运行时更快、却让编译更慢(C/Rust 这类区分构建期与运行期的语言尤其如此)——这类影响一定要写,因为看 diff 看不出来。
为什么值得做:git bisect
好提交信息 + 语义干净的提交直接决定 git bisect 的威力:二分定位「哪个提交引入 bug」时,如果命中的提交信息为零、或 diff 混着一堆无关改动,定位了也白搭。若能用测试脚本复现 bug,git bisect run <script> 还能全自动跑完。这是「提交拆干净」最实际的回报之一。
用 LLM 写提交信息
直接把 diff 丢给 LLM,它只能看到「做了什么」、看不到「为什么」,产物是描述性的——与需求正好相反。正确做法:在用 LLM 协助改动的同一个会话里写提交(对话天然包含上下文);或者把 diff、本节的写作要求一起给它,并附一句「缺上下文就问我」——让它把你当作读取上下文的「工具」,做成互动式问答。
练习
学习材料在 exercises.md。
改编自 MIT The Missing Semester 课程 Lecture 8: Beyond the Code(讲义 + 口播稿,CC BY-NC-SA 4.0):https://creativecommons.org/licenses/by-nc-sa/4.0/ · 课程站点:https://missing.csail.mit.edu/ · 讲座视频:https://www.youtube.com/watch?v=2DOEATfXT8k