
1.3.5 重复
在本节刚开始时,笔者用洗头打比方来说明程序开发的各个环节。开发程序和洗头的一个区别在于,它不是只重复一次,有时要重复许多次。
我们很少能够一遍就把程序写好。在大多数情况下,我们都得把其中的某些环节重复很多遍。比方说,在编辑完源代码并开始编译的时候,发现编译器报错了,这时,我们必须回到编辑环节,找到源代码里面有错的地方并修改错误,然后重新编译,直到编译器不再报错为止。编译环节结束之后,需要运行该程序并验证其执行结果,如果程序输出的内容不正确,或者程序在运行的过程中崩溃了,还是得回到编辑环节,找到源代码里面导致程序功能错误或发生崩溃的位置,并加以修改,然后重新编译、运行并验证。
听上去是不是很麻烦?确实是这样,如果你根本不知道代码里面什么地方有错,或者看不懂编译器或计算机给出的错误信息,那这个过程尤其困难。
许多年之前,编译器是相当原始的,而且特别生硬,不像今天这样通融(其实今天的编译器依然不会放过我们的错误,只不过它会用我们能够理解的方式更加明确地指出代码中的错误)。笔者当年在Digital Equipment制作的VAX(Virtual Address Extension)计算机上头一次用VMS(Virtual Memory System)系统里的C语言编译器编译自己的Hello, world!程序时,竟然遇到了23 000条错误信息,最后发现,这只不过是因为代码里少了个分号而已。仅因为缺一个字符,就报了这么一堆大错误,真是特别夸张。
举这个例子是想告诉大家,我们在编程时确实会出现错误(而且编译器与操作系统针对这些错误所给出的反馈信息可能比较奇怪),这让我们很难迅速确定错误原因,其实有的时候,只不过是因为漏了标点,或者把标点或变量名写错了。在学习编程的过程中,我们要掌握这样一项技能,也就是要学会面对刚才说的那种困难,我们要像侦探一样,从细微的地方看到问题,找出程序出错的真正原因。遇到困难的时候,可以先出去走走,放松一下,然后再回来工作。
谈谈调试
反复执行程序开发的各个环节会让你逐渐熟悉开发程序所用的语言及工具,同时也逐渐熟悉你自己(没错,学习编程的过程中,你会更清楚地了解自己),久而久之,你应该就能相当自然地运用这门语言与这些工具了。当然,你以后可能还是会打错字,或是写出一个计算结果明显有误的程序,但这些情况都不算bug(程序缺陷),它们只能叫作mistake(失误)。bug比这一类问题更加微妙。
bug是一种很能迷惑人的陷阱,初学编程的人不太容易发现这种问题。之所以产生bug,很可能是因为开发者在缺乏足够理由的情况下就做出了自以为是的论断,并根据这样的论断来编写代码,从而导致程序的逻辑出现缺陷。在笔者的工作经历中,最难发现的bug,基本上都是因为这种错误而产生的,也就是说,笔者自以为程序应该按照某种方式(或某套逻辑)来运作,于是就据此编写了代码,但实际上,自己在做出这样的认定时并没有进行验证。等笔者意识到这个问题之后,就会回过头去重新审视当初的论断,并通过代码予以验证,到了这一步,其实笔者就已经把自己当初挖的那个坑给绕过去了。
那么,这样的陷阱可以避开吗?
其实是可以的。在本书中,笔者会告诉大家一套方法,让我们在开发程序的过程中正确地处理这些微妙的问题。我们会通过试错、有方向地探索,以及边观察边寻找线索等方式来应对此类问题。有时我们会故意把程序弄错,看看这样修改会产生什么结果,以便在将来遇到类似的错误结果时能够联想到出错的原因。我们对于书中所要实现的每项功能都会做出验证,看看预期的效果与程序的实际效果是否相符。
当然,这并不是说只要采用这套方法来开发程序就一定能避开所有的bug。就算特别小心,也依然会遇到bug,但只要我们在做出判断时谨慎一些,能够仔细观察程序的行为,并收集线索来验证这个判断是否正确,那么大多数bug还是可以避开的。