C.1 编译时错误
最佳的调试方式是不用调试,因为你将错误扼杀在了摇篮中。为此,可采用 6.2 节中介绍的渐进开发,其中的关键是先编写一个可运行的简单程序,再每次添加少量的代码,这样发生错误时,你就会非常清楚它们出现在什么地方。
尽管如此,你可能还是会遭遇下面的情形。对于每种情形,我们都提供了一些处理建议。
C.1.1 编译器显示大量的错误消息
编译器显示 100 条错误消息时,并不意味着程序存在 100 个错误。编译器遇到错误时,通常会暂时反应不过来。过了第一个错误后,它会尝试再次理清思路,但有时会错报错误。
只有第一条错误消息是确实可靠的。因此我们建议你每次只修复一个错误,并立即再次编译程序。你可能发现,添加一个分号或大括号就消除了 100 个错误。
C.1.2 编译器显示怪异的错误消息,怎么都消除不掉
首先,仔细阅读错误消息。错误消息可能包含简洁的专业术语,但通常隐藏着重要的信息。
即便没有提供其他信息,消息也至少指出了问题出现在程序的什么地方。实际上,它指出的是编译器阅读到什么地方时发现了问题,但错误并非一定就在那里。将编译器提供的信息作为参考,如果在编译器所说的地方没有发现错误,就扩大搜索范围。
错误通常出现在错误消息指出的位置的前面,但也可能出现在其他地方。例如,如果错误消息指出方法调用有问题,实际的错误很可能出现在方法定义中。
如果不能迅速找到错误,可先缓口气,再在整个程序中查找。为此,要先确保正确地缩进了程序代码,这样更容易发现语法错误。
然后,开始查找常见的语法错误。
(1) 检查所有的小括号和方括号都是成对的且嵌套正确。所有的方法定义都必须嵌套在类定义中。所有的程序语句必须位于方法定义中。
(2) 别忘了,Java 区分大小写。
(3) 检查语句末尾的分号。另外,大括号后面不需要分号。
(4) 确保代码中的所有字符串的引号都是成对的;确保使用了双引号来括起字符串,并使用了单引号来括起字符。
(5) 对于每条赋值语句而言,确保左边的类型与右边的类型相同。确保表达式的左边是变量名或其他可赋值的东西(如数组元素)。
(6) 确保每个方法调用指定的实参的类型和排列顺序是正确的,且用来调用方法的对象的类型也是正确的。
(7) 调用值方法时,确保使用了它返回的结果;调用 void 方法时,确保没有使用它返回的结果。
(8) 调用实例方法时,确保调用它的对象的类型是正确的;在类外面调用其静态方法时,确保用句点表示法指定了类名。
(9) 在实例方法中,可在没有指定对象的情况下引用实例变量。如果你在静态方法中这样做(无论是否使用 this),将出现类似于下面的错误消息:non-static variable x cannot be referenced from a static context(在静态方法中不能引用非静态变量 x)。
如果还是没有找到错误,请进入下一节。
C.1.3 怎么做都无法让程序通过编译
如果编译器说存在错误,但你找不出来,可能是因为你和编译器看的代码不同。请检查开发环境,确保你编辑的程序就是编译器编译的程序。
程序有多个版本时,经常会出现这样的情况。你编辑的是一个版本,而编译的是另一个版本。
如果你不确定是不是这样的,可在程序开头故意添加一个显而易见的语法错误,再重新编译。如果编译器没有发现新添的错误,就很可能是开发环境的设置有问题。
如果你仔细研究了代码,并确定编译器编译的是正确的源代码文件,就该拿出杀手锏了:二分调试(debugging by bisection)。
备份当前调试的文件。如果调试的是 Bob.java,就创建副本并将其命名为 Bob.java.old。
将 Bob.java 的代码删除约一半,再重新编译。
如果程序能够通过编译,就说明错误发生在被删除的代码中。将删除的代码恢复一半,并再次进行编译。
如果程序不能通过编译,那么错误肯定出在刚恢复的代码中。将刚恢复的代码删除一半,并再次进行编译。
找到并修复错误后,再逐步恢复被删除的代码。
这种做法一点都不优雅,但找到错误的速度可能比你想象得快,而且非常可靠。也适用于其他编程语言!
C.1.4 按编译器说的做了,但还是不管用
有些错误消息还包含修复建议,如“Golfer 类必须声明为抽象的,它没有定义接口 java.lang.Comparable 中的方法 int compareTo(java.lang.Object)”。这让你以为编译器要求你将 Golfer 声明为抽象类;如果你还处于阅读本书的水平,很可能不知道抽象类是什么,也不知道该如何声明为抽象类。
好在编译器错了。对于这里的错误,解决方案是确保 Golfer 定义了将 Object 作为参数的方法 compareTo。
可别让编译器牵着鼻子走。错误消息表明存在错误,但推荐的解决之道并不可靠。
