引用 Dijkstra 经典论述指出测试通过≠代码正确;AI 生成代码往往通过浅层测试却隐藏深层缺陷,需要主动设计能破坏代码的测试用例。
绿色的测试套件不意味着你的代码是正确的;它意味着你还没有找到失败的情况。Dijkstra 的警告是提醒我们编写那些试图破坏代码的测试,而不是确认代码的测试。这个纪律在 AI 生成能通过简单测试但隐藏了会在现场失败的情况的代码时,显得尤为重要。
1970 年,Edsger Dijkstra 写下了每个工程师都应该记住的一句话:"程序测试可以用来显示 bug 的存在,但永远无法证明它们的不存在!"这句话很短,也很准确。失败的测试告诉你某些确定的信息:这里有缺陷。但通过的测试告诉你的远少于表面上看起来的那样。它只说明这个输入,在这个路径上,这一次没有失败。
通过的测试只证明你还没有找到缺陷。仅此而已。
大多数工程师读到这句话时会赞同,但随后的工作却像是相反的道理。测试套件变绿了,任务就感觉完成了。但绿色并不是正确性的证明。它只意味着这一次没有抓住失败。这两者之间的差距是大多数生产 bug 的来源——你没有想到的输入、你没有尝试过的排序顺序、和你桌子上的表现不同的电路板。
绿色是未被捕获的失败的缺失,而不是正确性的存在。
AI 拉大了这个差距。一个模型可以在几秒钟内生成一个函数和一套通过的测试,而两者都可能以同样的方式是错误的。代码处理了测试检查的情况;测试检查了代码处理的情况;两者都没有涉及在现场失败的边界。一切看起来都完成了。这个工具可以通过你的测试而不理解你的意图,因为它是在为绿色的结果而努力,而不是为一个正确的系统而努力。
这个工具可以让代码和测试相互一致,而两者仍然可能都是错误的。
所以 Dijkstra 那句话背后的纪律现在是一项核心工程技能,而不仅仅是一个好的做法。当代码和测试都很容易生成时,稀缺的能力是那种去寻找什么仍然是破损的能力。这是一个科学的习惯:你不试图确认你的想法,你试图破坏它,只有在它幸存了诚实的失败尝试后,你才对它稍微更信任一些。
不要试图确认你的代码。试着破坏它。
转变是从编写与你的代码一致的测试转向编写试图破坏它的测试。以下是一些具体的习惯:
对于每个函数,问问什么输入会破坏它,然后首先编写那个测试。如果你想不到一个,你还没有充分理解这个函数。
测试边界,而不是中间:空输入、最大值、off-by-one、并发访问、没人运行的错误路径。
声明代码必须始终保持的不变量,然后编写一个唯一的工作就是违反它的测试。
当 AI 给你带有通过测试的代码时,把那当作审查的开始,而不是结束。添加模型没想到要检查的情况。
当 bug 到达生产时,在写修复之前写一个能捕获它的测试。那个测试比修复更有价值。
所有这些都不意味着测试不值得写;它们是我们拥有的最好的工具之一。它意味着你应该准确知道通过的测试告诉你什么以及不告诉你什么。测试套件是一份记录,记录的是你专门去寻找的失败,范围不超过你自己对可能出错的理解。拓宽那个范围,测试就会更强大。跳过它,绿色就成了一个虚假的保证,它将缺陷隐藏起来,直到用户为你发现它。
在你最有把握的代码上看得最仔细。
学会阅读和破坏代码,不仅仅是运行它,并把通过的测试当作一个问题而不是一个答案。
基于 Edsger W. Dijkstra,《结构化编程笔记》(EWD249),1970 年。原文首发于 TECH VEDA。