三条准则:

  1. 不要使用异常机制处理正常业务逻辑;
  2. 异常的使用要符合具体的场景;
  3. 具体的异常要在接口规范中声明和标记清楚。

1. 异常就是非正常

异常状况的处理会让代码的效率变低。不应该使用异常机制来处理正常的状况。

2. 分清异常的类别

非正常异常(Error)–错误

这类异常的命名以 Error 结尾,比如 OutOfMemoryError,NoSuchMethodError。

这类异常,编译器编译时不检查,应用程序不需要处理,接口不需要声明,接口规范也不需要纪录;

运行时异常(RuntimeException)----异常

这类异常的命名通常以 Exception 结尾,比如 IllegalArgumentException,NullPointerException。 又称为非检查型异常(UncheckedException)

这类异常,编译器编译时不检查,接口不需要声明,但是应用程序可能需要处理,因此接口规范需要记录清楚;

检查型异常(CheckedException)-- --异常

除了运行时异常之外的其他的正常异常都是非运行时异常,比如 InterruptedException,GeneralSecurityException。和运行时异常一样,命名通常以 Exception 结尾。又称为非运行时异常。

这类异常,编译器编译时会检查异常是否已经处理或者可以抛出,接口需要声明,应用程序需要处理,接口规范需要记录清楚。

Java异常栈的形成过程 java异常处理规则(新手必看)_编译器

下面这个例子中,IllegalArgumentException 是运行时异常。虽然方法的声明中没有出现 IllegalArgumentException,但是**在方法的规范中,需要使用记录什么情况下抛出该异常。**只有这样,方法的调用者才能知道什么时候异常会抛出,该采取什么样的处理办法

/**
 * Check if the user name is a registered name.
 *
 * @return true if the userName is a registered name.
 * @throws IllegalArgumentException if the user name is invalid
 */
boolean isRegisteredUser(String userName) {
    // snipped
}

下面这个例子中,CloneNotSupportedException 是检查型异常。这样的异常,一定要出现在对应方法的声明中。

/**
 * Returns a clone if the implementation is cloneable.
 *
 * @return a clone if the implementation is cloneable.
 *
 * @throws CloneNotSupportedException if this is called on an
 *         implementation that does not support {@code Cloneable}.
 */
public Object clone() throws CloneNotSupportedException {
    // snipped
}

3. 标记清楚抛出异常

对于检查型异常,编译器或者 IDE 会友好地提醒使用合适的声明。

运行时异常很难发现,尤其是层次结构较多时。其中最常见的问题包括:

  1. 如果参数 userName 是一个无效引用(null),会发生什么状况,该怎么处理?
  2. 如果参数 userName 是一个空字符串(“”),会发生什么状况,该怎么处理?
  3. 如果参数 userName 不是一个规范的用户名,会发生什么状况,该怎么处理?

解决办法:

  1. 对于所有的可能抛出运行时异常,都要有清晰的描述,一个也不要错过;
  2. 查看所有的调用方法的规范描述,确认抛出的异常要么已经处理,要么已经规范描述。

4. 处理好捕获异常

Java 异常的四个要素:

  1. 异常类名(IllegalArgumentException, FileNotFoundException)–出了什么错
  2. 异常描述(“Invalid file path”)–为什么会出错
  3. 异常堆栈(at sun.security.ssl.InputRecord.read(InputRecord.java:504))–什么地方出了错,JVM 自动处理异常堆栈
  4. 异常转换(Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?)–记录了不同场景对这三个问题的不同理解和不同处理。

特别关注

  1. 对于异常类名,要准确地选择异常类。除非是超级的接口,否则应该尽量减少超级异常类的使用,而是选择那些意义明确、覆盖面小的异常类
  2. 对于异常描述,要清晰地描述异常信息。
  3. 对于异常转换,要恰当地转换异常场景,随着使用场景调整异常。带来的麻烦:
  1. 需要编写转换的代码
  2. 信息的冗余
  3. 信息的丢失,除非有明确的需求,要尽量保留所有的异常信息以及转换场景。