编程是个复杂的过程,而且因为由人来完成,所以难免出现错误。由于一些特殊的原因,编程错误称为“bug”,而跟踪和修正错误的过程称为“debugging”,中文叫做调试。
程序中会出现几种不同类型的错误,分清这几类错误有助于快速找出问题。
编译器只能翻译语法正确的程序,当存在语法问题时,编译失败,你也就无从运行程序了。语法指程序的结构和结构的规则。
例如,英语中的句子必须以大写字母开头并以句号结尾。不以大写字母开头或者不以句号结尾的句子在语法上都是错误的。
对大多数读者而言,语法错误不是个严重问题,我们读e e cummings的诗歌时并不会感觉到很多语法错误就是这个原因。
编译器可没这么宽容。程序中不管哪里出现了一个语法错误,编译器都会打印错误信息并退出,结果就是没办法执行程序。
更麻烦的是,C++中的语法规则比英语要多得多,而且编译器给出的错误提示信息不见得总有用。在我们刚学着编程的前几周,你可能要花很多时间来查找语法错误。随着经验的增长,你犯的错会越来越少,找出错误也会更快。
第二类错误是运行时错误,因为这类错误在程序运行时才会出现。
下面几周我们编写的都是很简单的程序,运行时错误非常少见,可能过一段时间才会遇到。
第三类错误是逻辑或语义错误。如果程序中有逻辑错误,程序仍会正确编译并运行,编译器不会生成任何错误消息,但是程序运行得不到预期结果。程序执行的不是你需要的功能。其实,你让程序做什么它就做什么,问题在于,你写出的代码和你本来要设计的功能并不一致。也就是说,程序的语义错了。识别逻辑错误可能很复杂,因为这需要你根据程序的输出和找出程序到底在做什么来倒推问题所在。
调试应该是你能从本书中学到的最重要的一个技能。虽然调试过程中可能有挫败感,但调试是编程中最具智慧、挑战和乐趣的部分之一。
从某种角度看,调试就像侦探工作。你要根据线索来推理各种过程和事件,最终找到结果。
调试又像做实验。一旦意识到出了问题,你就要修改程序并重新尝试。如果所做的假设正确,你就能预测对修改后的结果,这就离正确的程序又近了一步。如果假设错误,你就要提出新的假设。就像夏洛克•福尔摩斯所说的,“排除了那些不可能的之后,无论剩下什么,即使再不可思议,也一定是真相”(出自柯南道尔的《四签名》一书)。
对某些人而言,编程和调试是一回事。编程就是逐步调试程序直到它满足要求为止。这其中的理念是,总是从一个实现部分功能、可以工作的程序开始,然后加以小的改进并随手调试通过,这样保证总是有一个可用的程序。
比如Linux,它是个包含成千上万行代码的操作系统,最开始却是Linus Torvalds为探索Intel 80386芯片的功能而开发的一个简单程序。据Larry Greenfield所说,“Linus Torvalds早期有个项目,是交替打印AAAA和BBBB的程序,这个程序后来发展为了Linux”(出自The Linux Users' Guide Beta版1)。
后续章节会有更多有关调试和其他编程实践的建议。