八、异常处理

第57条 只针对异常的情况才使用异常

异常应该只用于异常的情况下,它们永远不应该用于控制流。

  1. 如果类具有 在某种特定状态下,才能够被调用的方法。则最好为之提供一个“状态”测试的方法,以避免去处理异常。如:Iterator的next()方法与hasNext()方法。

  2. 某种特定的异常状态下,也可以返回一个特殊值,进行处理。称为“可识别的返回值”方法。

怎么选择?
如果对象将在多线程同步访问的情况下,则应该使用“可识别的返回值”方法。因为在“状态测试”方法和调用之前有可能对象的状态被其他线程修改了。

如果“状态测试”方法与“状态相关”方法有功能实现上的重叠,则基于性能考虑,应该使用“可识别的返回值”方法。

其他情况下,一般应该选择“状态测试”方法,可以使代码有更好的可读性,使bug变得很明显。如果忘了调用状态测试方法,将会抛出异常。而如果忘了检查可识别的返回值,bug就很难发现。

第58条 对可恢复的情况使用受检异常,对编程错误使用运行时异常

Java中三种可抛出结构:

  1. 受检异常(checked exception)
  2. 运行时异常(run-time exception)
  3. 错误(error)

受检异常强迫调用者在catch子句中处理异常,或是传播出去。以此强制用户从异常条件中恢复。
非受检异常,是不需要也不应该被捕获,如果程序抛出非受检异常或是错误,往往就属于不可恢复的情形,继续执行下去是无意义的。程序抛出非受检异常或是错误,将会导致当前线程停止 halt,并出现适当的错误消息。

第59条 避免不必要地使用受检异常

把受检异常变成非受检异常的一种方法是,把此方法分成两个方法,第一个方法返回一个boolean表示是否应该抛出异常。如下:

if(obj.actionPermitted(args)){
	obj.action(args);
} else {
	// Handle exceptional condition
}

这种做法降低了客户端的使用成本。

第60条 优先使用标准的异常

常用异常:
IllegalArgumentException
IllegalStateException
NullPointerException
IndexOutOfBoundsException
ConcurrentModificationException
UnsupportedOperationException

第61条 抛出与当前类相对应的异常

异常转译(exception translation)
方法的高层实现应该捕获低层的异常,同时抛出这一层进行解释的异常。

异常链(exception chaining)
如果低层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。低层的异常原因被传到高层的异常中,高层的异常提供访问方法 Throwable.getCause 来获得低层的异常,如:

try{
	//... invoke lowerLvl method
} catch (LowerLvlException cause) {
	throw new HigherLvlException(cause);
}

第62条 每个方法抛出的异常都要有文档

始终要单独地声明受检异常,并且利用Javadoc的@throw标记,准确地记录下抛出每个异常的条件。
每个方法的文档应该描述它的前提条件,在文档中记录下未受检的异常是满足前提条件的最佳做法。
对于接口中的方法,在文档中记录可能抛出的未受检异常显得尤为重要。这份文档构成了该 接口的通用约定general contract的一部分,它指定了该接口的多个实现必须遵循的公共行为。

第63条 在细节消息中包含能捕获失败的信息

当程序由于未被捕获的异常而失败的时候,系统会自动地打印出该异常的堆栈轨迹。其中包含该明异常的字符串表示法,即它的toString结果:通常包含该异常的类名,跟着的是细节消息detail message。

为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值。

为了确保在异常的细节信息中包含足够的能查找到失败原因的信息,一种办法是在异常类的构造器中,引入这些信息,之后放到消息描述中,就可以自动产生细节消息。如:

public IndexOutOfBoundsException(int maxIndex, int index) {
	super("maxIndex: " + maxIndex +"," +
		"index: " + index)
		this.maxIndex = maxIndex;
		this.index = index;
}

第64条 努力使失败保持原子性

一般而言,方法调用失败之后,应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性(failure atomic)

几种实现的方法:

  1. 设计一个不可变对象
  2. 在可变对象中,实现失败原子性最常的方法是,在执行操作之前检查参数的有效性。
    变体:调整计算处理的顺序,使得任何可能会失败的计算部分都在对象状态被修改之前发生。
  3. 不太常用的方法:写一段恢复代码,发生失败时,执行此段代码以回滚到操作开始之前的状态上。这种方法主要用于永久性的数据结构(如磁盘操作)
  4. 在对象的copy上执行操作,操作完毕再用copy对象代替原来的对象。

实现失败原子性时,要注意不要增加开销和复杂性。

一般而言,作为方法规范的一部分,产生任何异常应该保持失败原子性。如果违反了,API文档就应该清楚地指明对象将会处于什么样的状态。可惜现在大量现有的API文档都未能做到这一点。

第65条 不要忽略异常

不要捕获了一个异常,却在catch块中什么都不实现。这样会掩埋可能的错误,使程序继续错误地运行下去。

实在不知道怎么处理异常的时候,至少打印一下异常信息,并传播出去,至少会在上层得到处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛克Lee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值