31.靠巧合编程

2018/11 18 00:11
31.靠巧合编程

你有没有看过老式的黑白战争片?一个疲惫的士兵警觉地从灌木丛里钻出来。前面有一片空旷地:那里有地雷吗?还是可以安全通过?没有任何迹象表明那是雷区—-没有标记、没有带刺的铁丝网、也没有弹坑。士兵用他的刺刀戳了戳前方的地面,又赶紧缩回来,以为会发生爆炸。 没有。于是他紧张地向前走了一会儿,剌刺这里,戳戳那里。最后,他确信这个地方是安全的,于是直起身来,骄傲地正步向前走去,结果却被炸成了碎片。

士兵起初的探测没有发现地雷,但这不过是饶幸。他由此得出了错误的结论——结果是灾难性的。

作为开发者,我们也工作在雷区里。每天都有成百的陷阱在等着抓住我们。记住士兵的故事, 我们应该警惕,不要得出错误的结论。我们应该避免靠巧合编程—-依靠运气和偶然的成功—-而要深思熟虑地编程。

怎样靠巧合编程

假定 Fred 接受了一项编程任务。他敲人一些代码,进行试验,代码好像能工作。他又敲入一些代码,进行试验,代码好像还能工作。在进行了几周这样的编码之后,程序突然停止了工作, Fred 花了数小时设法修正它,却仍然不知道原因何在。他可能会花上大量时间四处检杳这段代码, 却仍然无法修正它。不管他做什么,代码好像就是不能正确工作。

Fred 不知道代码为什么失败,因为他一开始就不知道它为什么能工作。假定进行的是 Fred 所做的有限“测试”,代码好像能工作,但那不过是一种巧合。受到错误信心的鼓动,Fred冲进了头脑空白的状态。现在,大多数聪明人可能都知道有人像 Fred,但我们更知道。我们不能依靠巧合—-对吗?

有时我们可能会依靠巧合。有时要把 ”幸运的巧合”目的的计划混为一谈实在很容易。 让我们来看一些例子。

实现的偶然。

实现的偶然是那些只是因为代码现在的编写方式才得以发生的事情。你最后会依靠没有记入文档的错误或是边边界条件。

假定你用坏数据调用一个例程。例程以一种特定的方式加以响应,而你的代码就以该响应为基础。但原作者并没有预期该例程会以那样的方式工作—-它甚至从未被考虑过。当例程被“修正”时,你的代码可能就会出问题。在最为极端的情况下,你调用的例程甚至没有被设计成能做你想要做的事情,但看起来它却工作得很好。以错误的次序、或是在错误的语境中进行调用,是一个与之相关的问题。

看来Fred在不顾一切地设法把某样东西显示在屏幕上。但这些例程从没有被设计成按这样的方式进行调用;尽管它们看起来能工作,但那实在只是一个巧合。

雪上加霜,当组件终于得以绘制出来,Fred不会再回去找出似是而非的调用。“它能能工作了,最好不要再画蛇添足……”

我们很容易被这样的思路愚弄。你为什么要冒把能工作的东西弄糟的风险呢?嗯,我们可以考虑几条理由:

1、它也许不是真的能工作—-它也许只是看起来能工作
2、你依靠的边界条件也许只是一个偶然。在不同的情形下(或许是不同的屏幕分辨率 ), 它的表现可能就会不同。
3、没有记人文档的行为可能会随着库的下一次发布而变化。
4、多余的和不必要的调用会使你的代码变慢。
5、多余的调用还会增加引人它们自己的新bug的风险。

对于你编写给别人调用的代码,良好的模块化以及把实现隐藏在撰写了良好文档的小接口之后, 这样一些基本原则都能对你有帮助。良好制订的合约(参见“按合约设计”,86 页)有助于消除误解。

对于你调用的例程,要只依靠记入了文档的行为。如果出于任何原因你无法做到这一点,那就充分地把你的各种假定记入文档。

语境的偶然

你还可能遇到 “语境的偶然”。假定你在编写一个实用模块。只是因为你现在是在为GUI环境编写代码,该模块就必须依靠给你的GUI吗?你是否依靠说英语的用户?有文化的用户?你还依靠别的什么没有保证的东西?

隐含的假定

巧合可以在所有层面上让人误人歧途—-从生成需求直到测试。特别是测试,充满了虚假的因果关系和巧合的输出。很容易假定X是Y的原因,但正如我们在调试(69 页)中所说的:不要假定,要证明。

在所有层面上,人们都在头脑里带着许多假定工作—-但这些假定很少被记入文档,而且在不同的开发者之间常常是冲突的。并非以明确的事实为基础的假定是所有项目的祸害。

提示44
Don’t Program by Coincidence
不要靠巧合编程

怎样深思熟虑地编程

我们想要让编写代码所花的时间更少,想要尽可能在开发周期的早期抓住并修正错误,想要在一开始就少制造错误。如果我们能深思熟虑地编程,那对我们会有所帮助:

1、总是意识到你在做什么
2、不要盲目地编程。试图构建你不完全理解的应用, 或是使用你不熟悉的技术,就是希望自己被巧合误导。
3、按照计划行事,不管计划是在你的头脑中,在鸡尾酒餐巾的背面,还是在某个CASE工具生成的墙那么大的输出结果上。
4、依靠可靠的事物。不要依靠巧合或假定。如果你无法说出各种特定情形的区别,就假定是最坏的。
5、你的假定建立文档。“按合约设计”(86 页)有助于澄清你头脑中的假定,并且有助于把它们传达给别人。
6、不要只是测试你的代码,还要测试你的假定。不要猜测;要实际尝试它。编写断言测试你的假定(参见 “断言式编程” 97 页)。如果你的断言是对的,你就改善了代码中的文档。如果你发现你的假定是错的,那么就为自已庆幸吧。
7、为你的工作划分优先级。把时间花在重要的方面;很有可能,它们是最难的部分。如果你的基本原则或基础设施不正确,再花哨的铃声和口哨也是没有用的。
8、不要做历史的奴隶。不要让已有的代码支配将来的代码。如果不再适用,所有的代码都可被替换。即使是在一个程序中,也不要你已经做完的事情约東你下一步要做的事情—-准备好进行重构(参见“重构”,149 页)这一决策可能会影响项目的进度。我们的假定是其影响将小于不进行改动造成的影响。
所以下次有什么东西看起来能工作,而你却不知道为什么,要确定它不是巧合。