当程序由于未被捕获的异常而失败的时候,系统会自动地打印出该异常的堆栈轨迹,也就是异常对象的printStackTrace()方法的输出结果。在堆栈轨迹中包含该异常的字符串表示法(string representation),即它的toString方法的调用结果。它通常包含该异常的类名,紧随其后的是细节消息(detail message)。因此,异常类型的toString方法应该尽可能多地返回有关失败原因的信息,便于以后分析。
为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值。例如,IndexOutOfBoundsException异常的细节消息应该包含下界、上界以及没有落在界内的下标值。该细节消息提供了许多关于失败的信息。这三个值中任何一个或者全部都有可能是错的。实标的下标值可能小于下界或等于上界(“越界错误”),或者它可能是个无效值,太小或太大。下界也有可能大于上界(严重违反内部约束条件的一种情况)。每一种情形都代表了不同的问题,如果程序员知道应该去查找哪种错误,就可以极大地加速诊断过程。
虽然在异常的细节消息中包含所有相关的“硬数据(hard data)”是非常重要的,但是包含大量的描述信息往往没有什么意义。堆栈轨迹的用途是与源文件结合起来进行分析,它通常包含抛出该异常的确切文件和行数,以及堆栈中所有其他方法调用所在的文件和行数。关于失败的冗长描述信息通常是不必要的,这些信息可以通过阅读源代码而获得。
为了确保在异常的细节消息中包含足够的能捕获失败的信息,一种办法是在异常的构造器而不是字符串细节消息中引入这些信息。然后,有了这些信息,只要把它们放到消息描述中,就可以自动产生细节消息。例如IndexOutOfBoundsException并不是有个String构造器,而是有个这样的构造器:
public class IndexOutOfBoundsException {
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
super("Lower bound:" + lowerBound +
",Upper bound:" + upperBound +
",Index:" + index);
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
}
Java平台类库并没有广泛地使用这种做法,但是,这种做法仍然值得大力推荐。它使程序员更加易于抛出异常以捕获失败。实际上,这种做法使程序员不想捕获失败都难!这种做法可以有效地把代码集中起来放在异常类中,由这些代码对异常类自身中的异常产生高质量的细节消息,而不是要求类的每个用户都多余地产生细节消息。
正如第58条中所建议的,为异常的“失败捕获”信息提供一些访问方法是合适的(在上述例子中的lowerBound、upperBound和index方法)提供一些访问方法是合适的。提供这样的访问方法对于受检的异常,比对于未受检的异常更为重要,因为失败——捕获信息对于从失败中恢复是非常有用的。