三条准则:
- 不要使用异常机制处理正常业务逻辑;
- 异常的使用要符合具体的场景;
- 具体的异常要在接口规范中声明和标记清楚。
1. 异常就是非正常
异常状况的处理会让代码的效率变低。不应该使用异常机制来处理正常的状况。
2. 分清异常的类别
非正常异常(Error)–错误
这类异常的命名以 Error 结尾,比如 OutOfMemoryError,NoSuchMethodError。
这类异常,编译器编译时不检查,应用程序不需要处理,接口不需要声明,接口规范也不需要纪录;
运行时异常(RuntimeException)----异常
这类异常的命名通常以 Exception 结尾,比如 IllegalArgumentException,NullPointerException。 又称为非检查型异常(UncheckedException)
这类异常,编译器编译时不检查,接口不需要声明,但是应用程序可能需要处理,因此接口规范需要记录清楚;
检查型异常(CheckedException)-- --异常
除了运行时异常之外的其他的正常异常都是非运行时异常,比如 InterruptedException,GeneralSecurityException。和运行时异常一样,命名通常以 Exception 结尾。又称为非运行时异常。
这类异常,编译器编译时会检查异常是否已经处理或者可以抛出,接口需要声明,应用程序需要处理,接口规范需要记录清楚。
下面这个例子中,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 会友好地提醒使用合适的声明。
运行时异常很难发现,尤其是层次结构较多时。其中最常见的问题包括:
- 如果参数 userName 是一个无效引用(null),会发生什么状况,该怎么处理?
- 如果参数 userName 是一个空字符串(“”),会发生什么状况,该怎么处理?
- 如果参数 userName 不是一个规范的用户名,会发生什么状况,该怎么处理?
解决办法:
- 对于所有的可能抛出运行时异常,都要有清晰的描述,一个也不要错过;
- 查看所有的调用方法的规范描述,确认抛出的异常要么已经处理,要么已经规范描述。
4. 处理好捕获异常
Java 异常的四个要素:
- 异常类名(IllegalArgumentException, FileNotFoundException)–出了什么错
- 异常描述(“Invalid file path”)–为什么会出错
- 异常堆栈(at sun.security.ssl.InputRecord.read(InputRecord.java:504))–什么地方出了错,JVM 自动处理异常堆栈
- 异常转换(Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?)–记录了不同场景对这三个问题的不同理解和不同处理。
特别关注
- 对于异常类名,要准确地选择异常类。除非是超级的接口,否则应该尽量减少超级异常类的使用,而是选择那些意义明确、覆盖面小的异常类
- 对于异常描述,要清晰地描述异常信息。
- 对于异常转换,要恰当地转换异常场景,随着使用场景调整异常。带来的麻烦:
- 需要编写转换的代码
- 信息的冗余
- 信息的丢失,除非有明确的需求,要尽量保留所有的异常信息以及转换场景。