异常,精确计算机世界里的不安定份子。当然说的是异常的使用,似乎没有什么统一的标准。但是我觉得在一个团队或者是系统里面还是应该遵循一定的规矩。
无规矩不方圆。
下面列一下目前工作中总结的一些使用经验
首先看下Java异常的分类。
Java 异常的分类
- 受检异常 (Checked Exception). 异常的处理由编译器来保证,如果方法声明异常但是没处理则编译失败。
- 非受检异常 (Unchecked Exception). 编译器不会检查的一类异常
-
RuntimeException
. IndexOutOfBoundsException, IllegalArgumentException 等 -
Error
. 表示很严重的问题,应用自己也搞不定, 与代码编写者无关。 OutOfMemoryError 等。 不要继承Error来定义异常
-
异常机制提供了一种异常事件的冒泡处理机制,你可以选择你想处理这个异常的层次。 如果没有异常的话,你就要在很低的层次处理异常,并想办法通知上层
异常使用中的困惑
在我的使用或者团队大家的共识是,异常使用困惑的地方主要集中在下面两点:
- 什么时候使用受检异常,什么时候使用非受检异常
- 错误码和异常的使用
所以接下来主要围绕着两个困惑来进行解答
先来看看受检和非受检异常使用的时机,此处非受检指的是 RuntimeException。
受检异常的使用时机
Use checked exceptions for conditions from which the caller
can reasonably be expected to recover
- 当可以用来表示业务流转中的异常分支,给出更合理的错误提示时
- 当你觉得调用方可以解决异常时
- 当异常由外部不可控的因素导致时,例如
用户输入
,数据库异常
,文件不存在
,网络
等
当你面临上面的情景时可以采用受检异常
非受检异常的使用时机
Use runtime exceptions to indicate programming errors (Bugs) -
From Effective Java
RuntimeException
通常指程序运行时的错误,可以引申为调用方错误的使用了API或者类库设计者的问题。
Runtime exceptions can occur anywhere in a program, and in a typical one they can be very numerous. Having to add runtime exceptions in every method declaration would reduce a program's clarity. Thus, the compiler does not require that you catch or specify runtime exceptions -
From Oracle The Java™ Tutorials
上面说到RuntimeException
是程序中非常常见的错误,因此没有强制让编译器来检查这一类错误。但是程序员自己就需要做好预先检查的工作。RuntimeException
有个好处就是能够穿透到最上层,让最上层做决定。因此可以围绕下面几点来使用RuntimeException
- 当你处理不了底层声明的异常时,将底层异常转换为RuntimeException。
- 当用户调用的参数不符合程序预期,使用不当时
一个强有力的证明: IllegalArgumentException 定义的是 RuntimeException
错误码和异常使用的时机
在没有异常机制的程序语言中,可能比较依赖错误码等来作为业务层面的流程判断。但是在Java中提供了异常的处理机制,这种冒泡处理机制,让你可以选择你想处理这个异常的层次。 如果没有异常的话,你就要在很低的层次处理异常,并想办法通知上层。
总结来讲,错误码和异常的特点就是
- 异常是
强类型且类型安全
的分支控制技术 - 返回错误值则是
弱类型不安全
作为Java程序员,应该优先使用异常来反馈程序中的异常情况。
使用异常来区分正常的业务场景和错误的业务路径
public String foo() IOException, FileNotFoundException {
...
}
// 一个好的使用方法应该是这样
try {
foo();
} catch (IOException ioe) {
// 给出友好的提示信息
} catch (FileNotFoundException fe) {
// 给出友好的提示信息
}
一种异常和错误码的混合方案
定义一个业务异常,然后异常中添加字段来表示错误码。
enum ErrorCode {
INVALID_PARAM, INVALID_PATH, INVALID_STAUS;
}
class MyException extend Exception {
public MyException(String msg) {
super(msg);
}
}
// 使用场景
public String foo() throws MyException {
if (...) {
throw new MyException(ErrorCode.INVALID_PARAM.getName());
}
...
if (...) {
throw new MyException(ErroCode.INVALID_PATH.getName());
}
...
if (...) {
throw new MyException(ErrorCode.INVALID_STATUS));
}
}
错误码和异常使用的建议
- 同构系统里优先使用异常来表示错误。
- 比如两个Java类相互调用,方法之间就可以用异常来处理非正常情况,这样可以充分发挥异常类型的作用
- 异构系统的情况使用返回错误信息的方式。
- 比如提供一个webservice接口,就可以用不同的业务编码来表示错误情况
最后是异常使用的一些实践
异常处理的实践
- 记得释放资源。特别是数据库还有网络资源,使用 try-catch-finally
- 不要使用异常作控制流程之用
- 不要忽略异常
- 生产代码不要 printStatckTrace()
- 尽量缩小异常的捕获范围
具体来讲就是
try-catch-finally 释放资源
OutputStreamWriter out = ...
java.sql.Connection conn = ...
try {
...
} catch(SQLException sqlex) {
...
} catch(IOException ioex) {
...
} finally {
if (conn != null) {
try {
conn.close();
} catch(SQLException sqlex2) {
...
}
}
if (out != null) {
try {
out.close();
} catch(IOException ioex2) {
...
}
}
}
不要将异常用作控制流(if-else)
虽然异常本身具有流程控制的属性,但是直接作为类似 if-else这样的方式来使用还是不应该的
public void check(String filePath) FileNotFoundException, FileExistException {
...
}
// 使用
try {
check();
} catch (FileNotFoundException fne) {
// do-file not found things ..
} catch (FileExistException fee) {
// do-file found things
}
不要忽略异常
- 可以只打个日志,但是不要什么都不做
- 如果你什么都做不了,就不要抓或者转换为RuntimeException
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {} //忽略的异常,这样要不得。出了问题坑自己、坑队友
// 接收异常
try {
...
} catch (Exception e) { // 过分泛华的异常,不利于排查问题
...
}
// 抛出异常
try {
...
} catch (IOException ioe) {
throw new Exception(ioe); // 泛化了异常, 外层调用丢失了异常类型的优势
}
// 自定义异常
try {
...
} catch (SqlException sqle) {
throw new MyOwnException(); // 定义了新的异常,但是丢了原始异常信息
}
生产代码不要 printStackTrace();
// 不好的方式
try {
...
} catch (IOException e) {
e.printStackTrace();
}
try {
...
} catch (IOException e) {
logger.error("message here" + e);
}
try {
} catch (IOException e) {
logger.error("message here" + e.getMessage());
}
// 比较好的方式
try {
...
} catch (IOException e) {
logger.error("message here", e);
}
异常的捕获范围
- 循环的场景,注意try代码块的范围
// bad case
try {
while(rs.hasNext()) {
foo(rs.next());
}
} catch (SomeException se) {
...
}
// good case
while(rs.hasNext()) {
try {
foo(rs.next());
} catch (SomeException se) {
...
}
}
参考资料
http://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
http://stackoverflow.com/questions/6115896/java-checked-vs-unchecked-exception-explanation
http://www.javapractices.com/topic/TopicAction.do?Id=129
http://www.ibm.com/developerworks/cn/java/j-lo-exception/index.html
http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html