2026年6月22日,Martin Tournoij(arp242)发了一篇短文,标题只比经典原则改了一个字母——Fence变成Finger。这个文字游戏背后是一个真实的软件工程灾难现场。他被雇来接手一个已有13年历史的代码库,在他之前所有人都走了。git log里一共295行提交说明,剔除dependabot自动提交和”fix typo”之后,只剩167行,平均每月一行。没有设计文档,代码里几乎没有注释。遗留着未完成的重构、已删除功能的残骸,还有写好了但从未被任何页面引用的功能。
Tournoij管这个叫Chesterton的中指。“是的,我们做了所有这些奇怪的事,而且我们不打算告诉任何人为什么。哈哈,去你的吧。”
Chesterton’s Fence这个原则出自G.K. Chesterton 1929年的著作《The Thing》。寓言很简单:一个改革者看到一条路中间横着一堵墙,觉得这东西毫无用处,要把它拆掉。Chesterton说,先别急着拆——你得先搞清楚当初为什么建它。也许建墙的理由你还没看到,但那个理由曾经存在过。这个原则被软件工程社区反复引用,因为它精确命中了一个高频事故场景:有人删了一段”看起来没用”的代码,几个月后生产环境挂了,那段代码处理的是一个三年才触发一次的边界条件。没人知道为什么会在那里,因为当初写下它的人已经离职两年了。
arp242把Fence改成Finger不是随手玩梗。Tournoij经历的那个代码库,问题不在于代码写得烂——烂代码到处都是。问题在于没有任何人留下关于”为什么”的记录。13年的累积决策,所有的权衡、所有的历史约束、所有踩过的坑,全部蒸发。接手的人不是在”翻过一堵墙”——墙至少还有个可见的物理存在,暗示着”这里曾经有人做过一个决定”。他面对的是一个彻底的信息真空。这比墙糟糕得多。墙是沉默的提醒,中指是沉默的嘲弄。
这个区别触及了代码注释最核心的价值。代码本身已经说了”做了什么”——只要语言不是故意混淆的,逻辑是能读出来的。但代码永远无法说出”为什么选择了方案A而不是方案B”。那个诡异的workaround是因为某个版本的库有bug,那个看似多余的null检查是因为2019年某个周五下午的一次P0事故,那个奇怪的排序逻辑是因为下游系统对顺序有硬依赖而这个依赖本身是个历史错误。这些信息如果不在注释或commit message里,就永远消失了。Tournoij的质问很直白——写这些不是可选的额外工作,它就是开发工作的一部分。写得不好也没关系,英文不好也没关系,忘了某些细节也没关系,但至少得有”什么东西”。什么都没有,就是给所有后来的人竖中指。
Lobsters讨论区里ChrisDenton(18票)的评论把这个话题推到了组织层面。他指出了一个更隐蔽的困境:有时候在当时那个时刻,没人知道什么信息将来会变得重要。如果围绕一个决策的讨论没有被记录下来——不管是邮件、聊天记录还是issue——后来的”数字考古”就几乎无法进行。而当一个组织把开发者视为可互换零件时,这种脆弱性会被放大到极致。没有人待得足够久,没有人积累起对系统整体的直觉理解,同样的错误被反复犯下,重复造轮子成为常态。ChrisDenton的措辞很克制,但结论很锋利:开发者被视为可互换零件的组织,是最脆弱的组织。
david_chisnall(8票)从code review的角度补了一刀。他说code review最大的价值是迫使你把不显而易见的决策加上注释——发现bug是副产品。最大的价值是”第二个人迫使你把不显而易见的决策加上注释”。他自己会为那些他觉得不显然的地方写注释。reviewer会为reviewer觉得不显然的地方提问。两轮下来,注释覆盖了两个人各自认为需要解释的东西。后来的人读到这段代码时,理解概率不再为零。这个机制的巧妙之处在于它不依赖作者的自觉——它把知识留存嵌入了一个不得不执行的流程。
但每堵墙都该留着吗?问题也有反面。Steph Tulkens写过一篇《Chesterton’s Gap》——墙还没建呢,先建墙再说。过度保守同样有害:每个团队都见过那种没人敢碰的祖传代码,周围的逻辑已经变了三轮,那段代码当初处理的问题可能早已不存在,但因为”不知道当初为什么写”,所以一直留着。技术债不只是乱改代码造成的,不敢改代码同样会积累技术债。什么时候该拆墙,什么时候该留墙,没有一个算法能自动判断。判断力只能来自对系统足够深的理解——这恰好回到了ChrisDenton的论点:组织如果把开发者当可替换零件,连这种判断力都培养不出来。
以下是一个简化的判断框架,以提问清单的形式呈现:
| 维度 | 倾向于保留 | 倾向于拆除 |
|---|---|---|
| 上下文可获得性 | 原团队全部离职,无文档无注释 | 原决策者还在,可以当面问清楚 |
| 改动影响范围 | 涉及核心业务路径,出错后果严重 | 孤立模块,有完整测试覆盖 |
| 代码意图清晰度 | 有注释解释”为什么”,逻辑自洽 | 注释说的是”做什么”,且与代码行为不一致 |
| 触发频率 | 处理低频但高影响的边界条件 | 代码已被证明从未被执行 |
| 替代成本 | 重写需要重新发现所有边界条件 | 有明确的规格说明可以参照重写 |
这个表格不解决任何问题。它只是提醒:判断该不该拆墙,比拆墙本身需要更多的信息。
Tournoij那篇文章在Lobsters上拿到82票,不是因为他说了什么新东西。Chesterton’s Fence在软件工程圈被讨论了十几年。共鸣来自于他命名的那个情绪——写烂代码不一定带着恶意,但不留一句解释就离开,是对所有后来者的轻蔑。 每一个在凌晨三点被”那段看不懂的祖传代码”叫起来的开发者,都认得这根中指。修复它不需要更多的流程规范,只需要把commit message当成交付物的一部分。那167行的13年——那个数字本身,就是最有效的论证。