如何更好的使用异常

Java的基本理念是,“结构不佳的代码不能运行”java

        —— Java编程思想程序员

何时使用异常

发现错误的理想时机是在编译阶段,也就是试图运行程序以前。然而,编译阶段并不能找出全部的错误,余下的问题必须在运行期间解决。这就须要错误源可以经过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。编程

举个例子,当一个方法遇到一种状况,这种状况下,它不能知足约定,这时应该如何处理?数组

传统的作法是方法应该返回某种错误码。调用者被迫区检查错误,若是调用者也不能处理错误,那就给调用者的调用者返回一个错误码……架构

程序员并不老是检查和传递返回的错误码,结果错误没有被检测到,致使后面的严重破坏!并发

C以及其余早期语言经常具备多种错误处理模式,这些模式每每创建在约定俗成的基础之上,而并不属于语言的一部分。ide

只针对异常的状况才使用异常

从一个反面教材开始:测试

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实现原本可能要执行的某些特定优化。这个例子说明:atom

异常应该只用于异常的状况下,他们永远不该该用于正常的控制流;

设计良好的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的直接子类有这些:

  1. AnnotationTypeMismatchException
  2. ArithmeticException
  3. ArrayStoreException
  4. BufferOverflowException
  5. BufferUnderflowException
  6. CannotRedoException
  7. CannotUndoException
  8. ClassCastException
  9. CMMException
  10. ConcurrentModificationException
  11. DataBindingException
  12. DOMException
  13. EmptyStackException
  14. EnumConstantNotPresentException
  15. EventException
  16. IllegalArgumentException
  17. IllegalMonitorStateException
  18. IllegalPathStateException
  19. IllegalStateException
  20. ImagingOpException
  21. IncompleteAnnotationException
  22. IndexOutOfBoundsException
  23. JMRuntimeException
  24. LSException
  25. MalformedParameterizedTypeException
  26. MirroredTypeException
  27. MirroredTypesException
  28. MissingResourceException
  29. NegativeArraySizeException
  30. NoSuchElementException
  31. NoSuchMechanismException
  32. NullPointerException
  33. ProfileDataException
  34. ProviderException
  35. RasterFormatException
  36. RejectedExecutionException
  37. SecurityException
  38. SystemException
  39. TypeConstraintException
  40. TypeNotPresentException
  41. UndeclaredThrowableException
  42. UnknownAnnotationValueException
  43. UnknownElementException
  44. UnknownTypeException
  45. UnmodifiableSetException
  46. UnsupportedOperationException
  47. WebServiceException
相关文章
相关标签/搜索