两个错误导致一个正确(而且难以修复)

Two Wrongs Can Make a Right (and Are Difficult to Fix)

代码时不会说谎的,但它会自相矛盾。有些矛盾还会导致“这怎么可能跑得起来?”的时刻。

在一次采访中,阿波罗11号月球模块的首席设计师,Allan Klumpp透露,控制引擎的软件有个会导致着陆舱不稳定的bug。然而,另一个bug却弥补了它,在这些bug被发现并修复之前,软件都被应用于阿波罗11和12号登月。

设想一个函数会返回其完成状态。假设它本该返回true的时候却返回了false。继续假设刚好调用此函数的地方忽略了该返回值。它就会一直正常工作,直到某天某人注意到这里缺失检查,才会修改它。

再设想一个将状态存储为XML的程序。假设将原文本来应该是TimeToDie的节点写成了TimeToLive。若代码编辑器和阅读器都接纳了该错误,那每件事都看起来还好。但如果其中一方修复了,或者一个全新的应用程序读到此文档,代码的对称性就被打破了。

当代码中两个缺陷产生了一个可见错误时,按部就班地修复错误可能会导致自身崩溃。开发者得到一个bug反馈,找出缺陷并修复到,然后重新测试。然而反馈中的错误依然出现,因为第二个缺陷还在起作用。所以又把第一个修复已出,继续检查代码知道发现第二个隐藏bug,再次修复。可惜第一个bug又回来了,反馈的错误依然存在,再次回滚第二个修复。以此循环,此刻的开发者已全然不顾两个可能的修复结果,继续寻找第三处让程序不工作的地方。

两个代码间相互作用下只出现一个可见错误,不仅让问题难以修复,还容易让开发者陷入死胡同,只能发现他们早起尝试的正确答案。

这不仅仅是在代码中才会发生:问题也会存在于编写必要的文档中。它会病毒式地从一个地方扩撒到其它地方。代码中的错误会在文档中的描述错误的到弥补。

它同样会在人群中扩散:当用户学会了程序说“左键”其实意味着“右键”的时候,他们就会调整自己相应的行文。他们甚至会将其传递给新用户:“记住,当程序说‘点击左键’,它的真实意图是让你点击右键”。bug就这样因为用户的再培训突然间被修复了。

单个错误可以很容易标记并修复。但由多种原因引起的问题就需要多种变换,修复难度更高。关于这部分,是因为简单的问题人们倾向于快点修复它们,而更为困难的问题会记录下来延后处理。

对于如何处理由交叉缺陷引发的故障,没有简单的建议。要意识到这种可能性,要有清晰的头脑,并愿意考虑所有的可能性。

0%