異常酝静,精確計算機世界里的不安定份子。當然說的是異常的使用狼忱,似乎沒有什么統(tǒng)一的標準膨疏。但是我覺得在一個團隊或者是系統(tǒng)里面還是應該遵循一定的規(guī)矩。
無規(guī)矩不方圓藕赞。
下面列一下目前工作中總結的一些使用經(jīng)驗
首先看下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
- 當可以用來表示業(yè)務流轉中的異常分支儒喊,給出更合理的錯誤提示時
- 當你覺得調用方可以解決異常時
- 當異常由外部不可控的因素導致時镣奋,例如
用戶輸入
,數(shù)據(jù)庫異常
怀愧,文件不存在
侨颈,網(wǎng)絡
等
當你面臨上面的情景時可以采用受檢異常
非受檢異常的使用時機
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啤贩。
- 當用戶調用的參數(shù)不符合程序預期,使用不當時
一個強有力的證明: IllegalArgumentException 定義的是 RuntimeException
錯誤碼和異常使用的時機
在沒有異常機制的程序語言中拜秧,可能比較依賴錯誤碼等來作為業(yè)務層面的流程判斷痹屹。但是在Java中提供了異常的處理機制,這種冒泡處理機制枉氮,讓你可以選擇你想處理這個異常的層次志衍。 如果沒有異常的話,你就要在很低的層次處理異常聊替,并想辦法通知上層楼肪。
總結來講,錯誤碼和異常的特點就是
- 異常是
強類型且類型安全
的分支控制技術 - 返回錯誤值則是
弱類型不安全
作為Java程序員惹悄,應該優(yōu)先使用異常來反饋程序中的異常情況春叫。
使用異常來區(qū)分正常的業(yè)務場景和錯誤的業(yè)務路徑
public String foo() IOException, FileNotFoundException {
...
}
// 一個好的使用方法應該是這樣
try {
foo();
} catch (IOException ioe) {
// 給出友好的提示信息
} catch (FileNotFoundException fe) {
// 給出友好的提示信息
}
一種異常和錯誤碼的混合方案
定義一個業(yè)務異常,然后異常中添加字段來表示錯誤碼泣港。
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));
}
}
錯誤碼和異常使用的建議
- 同構系統(tǒng)里優(yōu)先使用異常來表示錯誤暂殖。
- 比如兩個Java類相互調用,方法之間就可以用異常來處理非正常情況当纱,這樣可以充分發(fā)揮異常類型的作用
- 異構系統(tǒng)的情況使用返回錯誤信息的方式呛每。
- 比如提供一個webservice接口,就可以用不同的業(yè)務編碼來表示錯誤情況
最后是異常使用的一些實踐
異常處理的實踐
- 記得釋放資源坡氯。特別是數(shù)據(jù)庫還有網(wǎng)絡資源晨横,使用 try-catch-finally
- 不要使用異常作控制流程之用
- 不要忽略異常
- 生產(chǎn)代碼不要 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); // 泛化了異常坪稽, 外層調用丟失了異常類型的優(yōu)勢
}
// 自定義異常
try {
...
} catch (SqlException sqle) {
throw new MyOwnException(); // 定義了新的異常曼玩,但是丟了原始異常信息
}
生產(chǎn)代碼不要 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);
}
異常的捕獲范圍
- 循環(huán)的場景鳞骤,注意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