点击上方 "程序员小乐"关注, 星标或置顶一起成长
每天凌晨00点00分, 第一时间与你相约
每日英文
To live a beautiful life, one must be tolerant, without complaint or explanation.
要生活得漂亮,需要付出极大忍耐,一不抱怨,二不解释。
每日掏心话
人活着,有一种煎熬的痛苦,并不是不曾拥有,也不是早已失去,而是患得患失。
来自:XuePeng77 | 责编:乐乐
链接:my.oschina.net/u/2450666/blog/1585393
正文
Java的基本理念是,“结构不佳的代码不能运行”
—— Java编程思想
什么时候使用异常
发现错误的理想时机是在编译阶段,也就是试图运行程序之前。然而,编译阶段并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能够通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。
举个例子,当一个方法遇到一种情况,这种情况下,它不能满足约定,这时应该如何处理?
传统的做法是方法应该返回某种错误码。调用者被迫区检查错误,如果调用者也不能处理错误,那就给调用者的调用者返回一个错误码……
程序员并不总是检查和传递返回的错误码,结果错误没有被检测到,导致后面的严重破坏!
C以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。
只针对异常的情况才使用异常
从一个反面教材开始:
try {
int i = 0;
while (true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}
上面这种情况就是乱用异常的典型,它的构想是非常拙劣的。当这个循环企图访问数组边界之外的第一个数组元素时,用throw、catch和忽略ArrayIndexOutOfBoundsException的手段来达到终止无限循环的目的,对于任何一个合格的程序员来说,它的实现用下面的标准模式一看便知:
for (Mountain m : range)
m.climb();
异常机制的设计初衷是用于不正常的情形。所以很少会有JVM实现试图对它们进行优化,使得异常与显示的测试一样快速,例如for等。把代码放到try-catch块中,反而阻止了JVM实现本来可能要执行的某些特定优化。这个例子说明:
异常应该只用于异常的情况下,他们永远不应该用于正常的控制流;
设计良好的API不应该强迫它为了正常的控制流而使用异常;
Java的异常架构
所有的异常类都派生自Throwable。当发生来某种异常,而这种异常不是期望应用程序处理的,比如内存耗尽等,则JVM会抛出Error。这种异常不推荐程序员使用。
一般程序员使用的异常属于Exception类的异常,他们分为两种:
未检查异常(unchecked exception),属于RuntimeException的子类,它属于不需要也不应该被捕获的可抛出结构。用运行时异常来表明编程错误。大多数的运行时异常都表示前提违例(precondition violation),意思是API的客户没有遵守API的规范建立的约定,比如要求参数不能为Null,但还是传递Null参数,实现的所有未检查异常(未受检异常)都应该是RuntimeException的子类;
已检查异常(checked exception),属于Exception的子类,也称为受检异常,如果期望调用者能够适当地恢复,对于这种情况就应该使用它。通过抛出异常,强迫调用者在一个catch里处理该异常,或将它传播出去,尤其在设计API时,设计者让API用户面对受检的异常;
异常也是个对象,可以在它上面定义任意的方法。这些方法的主要用途是为捕获异常的代码提供额外的信息,特别是关于引发这个异常条件的信息。
避免不必要的使用受检的异常
受检异常强迫使用该API的用户必须catch异常,大大增加来程序的可靠性。但是多度使用会使API使用起来非常不方便。用户必须声明它抛出的这些异常,或者让它们传播出去,无论哪种方法,都给API用户增添来负担。
那什么时候使用受检异常比较合适呢?
如果,正确的使用了API,但并不能组织这种异常条件的发生,并且一旦产生异常,使用API的用户会立即采取有用的动作,这种情况就是使用受检异常的典型场景。
例如,API的设计人员可以尝试着问自己:API用户将如何处理该异常,会有比下面代码更好的方式吗?
catch (CheckedException e) {
throw new AssertionError();
}
下面这种做法如何?
catch (CheckedException e) {
e.printStackTrace();
System.exit(1);
}
如果使用API的用户无法做的比这更好,那还是使用未受检异常更为合适。
这段代码的意思为,如果这个异常发生了,程序注定会停止,或者要完成的事情注定不可以完成,那直接抛出未受检异常让程序中断,暴露出问题更为合适。
例如,在缴费电话费时,由于余额不足导致缴费失败,可以抛出受检异常,并提供一个方法,查询所需的金额等。
优先使用标准的异常
代码重用是值得提倡的,这是一条通用的规则,异常也不例外。
Java提供来一组基本的未受检异常,它们满足了绝大多数API的异常需求。
异常 | 场合 |
IllegalArgumentException | 非null的参数值不正确 |
IllegalStateException | 对于方法调用而言,对象状态不合适 |
NullPointerException | 在禁止使用null的情况下参数值为null |
IndexOutOfBoundsException | 下标参数值越界 |
ConcurrentModificationException | 进行并发修改的情况下,检测到并发修改对象 |
UnsupportedOperationException | 对象不支持用户请求的方法 |
抛出与抽象相对应的异常
如果方法抛出的异常与它所执行的任务没有明显联系,这种情形将会使人不知所措。
更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。
如下所示:
try {
} catch (LowerLevelException e) {
throw new HigherLevelException(...);
}
或者:
Iterator i = ...
try {
return i.next()
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
还有一种特殊的异常转义形式,称为异常链(exception chaining),如果低层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。底层异常的原因可以传给高层异常,高层异常提供访问方法,来获得底层异常:
try {
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
尽管异常转移与不佳选择地从底层传递异常的做法相比有所改进,但是它也不能被滥用。
如果可能,处理来自跌穿那个异常的最好做法是,在调用底层方法之前,前确保他们会执行成功,从而避免它们抛出异常。
努力保持失败的原子性
当对象抛出异常后,通常期望这个对象仍然保持在一种定义良好的可用状态之中,即使失败是发生在执行某个操作过程中间,对于受检异常而言,这点尤为重要,因为API用户期望能从这种异常中恢复。
一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为具有失败原子性(failure atomic)。
有几种方法可以实现这种效果:
最简单的办法是设计一个不可变对象;
设计处理过程的顺序,使得任何可能会失败的部分都在对象状态改变之前发生;
编写一段恢复代码(recovery code);
在对象的一份临时拷贝上执行操作;
异常文档的重要性
始终要单独声明受检的异常,并且利用JavaDoc的@throws标记,准确地记录下抛出每个异常的条件。
永远不要throws Exception,或者throws Throwable,它们会掩盖该方法抛出的任何其他异常。
未受检的异常通常代表编程上的错误,让API用户了解所有这些错误都有助于帮助他们避免调用错误。对于方法可能抛出的未受检异常,如果将这些异常信息很好的组织成列表文档,就可以有效地描述出这个方法被成功执行的前提条件(precondition)。
对于接口中的方法,在文档中记录下它可能抛出的未受检异常显得尤为重要。这份文档构成了该接口的通用约定(general contract)的一部分,它指定来该接口的多个实现必须遵守的公共行为。
使用JavaDoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中。
如果一个类中的许多方法处于同样的原因抛出同一个异常,在类的文档注视中,堆这个异常建立文档。
不要忽略异常
空的catch达不到应有的目的,至少,catch块也应该包含一条说明,解释为什么忽略这个异常。
希望不要忽略异常。
附录A
java.lang.RuntimeException的直接子类有这些:
AnnotationTypeMismatchException
ArithmeticException
ArrayStoreException
BufferOverflowException
BufferUnderflowException
CannotRedoException
CannotUndoException
ClassCastException
CMMException
ConcurrentModificationException
DataBindingException
DOMException
EmptyStackException
EnumConstantNotPresentException
EventException
IllegalArgumentException
IllegalMonitorStateException
IllegalPathStateException
IllegalStateException
ImagingOpException
IncompleteAnnotationException
IndexOutOfBoundsException
JMRuntimeException
LSException
MalformedParameterizedTypeException
MirroredTypeException
MirroredTypesException
MissingResourceException
NegativeArraySizeException
NoSuchElementException
NoSuchMechanismException
NullPointerException
ProfileDataException
ProviderException
RasterFormatException
RejectedExecutionException
SecurityException
SystemException
TypeConstraintException
TypeNotPresentException
UndeclaredThrowableException
UnknownAnnotationValueException
UnknownElementException
UnknownTypeException
UnmodifiableSetException
UnsupportedOperationException
WebServiceException
欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。
欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。
猜你还想看
(二)手把手教你 SpringBoot + SpringCloud —— 使用Eureka实现服务注册与发现!
关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?