1.4 程序排错

先说一个坏消息:一旦开始写程序,就免不了要出错。程序设计虽然并不难,但无论是 初学编程者还是经验丰富的专业程序员,程序中出现各种错误都是很常见的。

再说一个好消息:计算机(严格说是编译器或解释器)能够帮助我们发现程序中的很多 错误。

在计算机行话中,程序中的错误被称为“臭虫”(bug),而发现并改正错误的过程称为排 错(debug,或称调试)。

程序中的错误大体可分为三种类型:语法错误、运行错误和语义错误。 编程语言和自然语言一样规定了一套语法规则,这些规则定义如何用符号组成形式上正

确的程序。只有符合语法规则的程序才能被计算机执行,语法不正确的程序根本就无法通过 编译器或解释器的检查,更谈不上正确执行了。自然语言中的语法比较宽松,犯点语法错误 一般不会影响交流,就像有人说的:研表究明,汉顺字序并不定一影阅响读。与自然语言不 同,编程语言的语法是非常严格的,任何一点语法错误(例如少了个逗号)都会导致程序无 法执行。初学一门编程语言的时候,肯定会出现很多语法错误,但随着对语言的熟悉和经验 的增加,语法错误会越来越少。例如:

  1. >>> 3 + 4 *
  2. SyntaxError: invalid syntax

显然,乘法运算符需要两个运算数,而上面的表达式中未提供足够的数据,因此导致了语法 错误。Python 解释器很容易发现语法错误,并将错误信息打印出来供程序员参考。

当程序通过了编译器或解释器的语法检查,就可以运行了。遗憾的是,程序语法正确并 不能保证程序执行成功,因为有很多仅在程序执行时才会出现的错误,这种运行错误也称为 异常(exception)。例如,如果程序中有一条执行除法运算的语句,那么在运行时就有可能发 生除数为 0 的错误,这种错误在编译阶段无法发现,因为除法算式是符合语法的。例如:

  1. >>> def f():
  2. x = 2
  3. print 10 / x
  4. x = x 2
  5. print 10 / x
  6. >>> f()
  7. 5
  8. Traceback (most recent call last):
  9. File "<pyshell#4>", line 1, in <module> f()
  10. File "<pyshell#3>", line 5, in f print 10 / x
  11. ZeroDivisionError: integer division or modulo by zero

上面这个函数显然没有任何语法错误,因此能够被 Python 执行,我们也看到了部分执行 结果:10 / 2 = 5 被正确地计算并显示出来。但是当 x 变为 0,再次计算 10 / x 时发生了运行 错误 ZeroDivisionError。

语义错误也称逻辑错误,是指程序在程序逻辑上出错,根本不是预定的功能。语法错误 和运行错误都可以被计算机检查出来,程序员根据计算机的报错信息可以比较容易地找出源 程序中的错误并纠正之。而语义错误可能很难发现。因此,有语义错误的程序往往能够“成 功”执行,不产生任何错误消息,但是程序的结果并不是我们想要的,或者说程序的意义(语 义)错了。例如,下面这个程序试图计算半径为 5 的圆的面积:

  1. >>> pi = 3.1416
  2. >>> r = 5
  3. >>> print pi * r
  4. 15.708

程序的执行没有产生任何错误,但结果根本不是圆的面积。

由于得不到 Python 解释器的帮助,查找语义错误往往是非常恼人的。一般来说我们需要 从头到尾仔细审查程序算法,找出可能的错误步骤。一种有用的排错方法是在程序中插入大 量的 print 语句,用来显示计算的中间结果。通过检查中间结果,可以将错误进行精确定位。 这些 print 语句的目的是帮助排错,一旦程序完全正确,再将它们从程序中删除。

排错是程序设计的最重要技能之一。找出程序中的错误一方面很令人头痛,另一方面也 很有乐趣,因为排错过程有点像侦探破案的过程——寻找线索、推断原因、最终定位错误。 如果情况很复杂,对某处代码是否错误不是很肯定,则可以利用试错法来排错:先试着修改 该处代码,然后运行程序看看结果如何。这个过程可以重复进行,直至确定错误为止。