Like Share Discussion Bookmark Smile

J.J. Huang   2020-05-09   Java   瀏覽次數:

《阿里Java開發手冊》 | 異常日誌 - 異常處理

【強制】Java 類庫中定義的可以通過預檢查方式規避的 RuntimeException 異常不應該通過 catch 的方式來處理,比如:NullPointerException,IndexOutOfBoundsException 等等。
說明:無法通過預檢查的異常除外,比如,在解析字符串形式的數字時,可能存在數字格式錯誤,不得不通過 catch NumberFormatException 來實作。
正例:if (obj != null) {…}
反例:try { obj.method(); } catch (NullPointerException e) {…}


【強制】異常不要用來做流程控制,條件控制。
說明:異常設計的初衷是解決程序運行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。


【強制】catch 時請分清穩定程式碼和非穩定程式碼,穩定程式碼指的是無論如何不會出錯的程式碼。對於非穩定程式碼的 catch 盡可能進行區分異常類型,再做對應的異常處理。
說明:對大段程式碼進行 try-catch,使程序無法根據不同的異常做出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。
正例:用戶註冊的場景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上作出分門別類的判斷,並提示給用戶。


【強制】捕獲異常是為了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化為用戶可以理解的內容。


【強制】事務場景中,拋出異常被 catch 後,如果需要回滾,一定要注意手動回滾事務。


【強制】finally塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch。
說明:如果 JDK7 及以上,可以使用 try-with-resources 方式。


【強制】不要在 finally 塊中使用 return。
說明:try 塊中的 return 語句執行成功後,並不馬上返回,而是繼續執行 finally 塊中的語句,如果此處存在 return 語句,則在此直接返回,無情丟棄掉 try 塊中的返回點。
反例:

1
2
3
4
5
6
7
8
9
10
private int x = 0;
public int checkReturn() {
try {
// x 等於 1,此處不返回
return ++x;
} finally {
// 返回的結果是 2
return ++x;
}
}

【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。
說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外情況。


【強制】在調用 RPC、二方包、或動態生成類的相關方法時,捕捉異常必須使用 Throwable類來進行攔截。
說明:通過反射機制來調用方法,如果找不到方法,拋出 NoSuchMethodException。什麼情況會拋出NoSuchMethodError 呢?二方包在類衝突時,仲裁機制可能導致引入非預期的版本使類的方法簽名不匹配,或者在字節碼修改框架(比如:ASM)動態建立或修改類時,修改了相應的方法簽名。這些情況,即使程式碼編譯期是正確的,但在程式碼運行期時,會拋出 NoSuchMethodError。


【推薦】方法的返回值可以為 null,不強制返回空集合,或者空對像等,必須添加註解充分說 明什麼情況下會返回 null 值。
說明:本手冊明確防止 NPE 是調用者的責任。即使被調用方法返回空集合或者空對象,對調用者來說,也 並非高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回 null 的情況。


【推薦】防止 NPE,是工程師的基本修養,注意 NPE 產生的場景:

  • 返回類型為基本資料類型,return 包裝資料類型的對象時,自動拆箱有可能產生 NPE。
    反例:public int f() { return Integer 對象}, 如果為 null,自動解箱拋 NPE。
  • 資料庫的查詢結果可能為 null。
  • 集合裡的元素即使 isNotEmpty,取出的資料元素也可能為 null。
  • 遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE。
  • 對於 Session 中獲取的資料,建議進行 NPE 檢查,避免空指針。
  • 級聯調用 obj.getA().getB().getC();一連串調用,易產生 NPE。
    正例:使用 JDK8 的 Optional 類來防止 NPE 問題。

【推薦】定義時區分 unchecked / checked 異常,避免直接拋出 new RuntimeException(),更不允許拋出 Exception 或者 Throwable,應使用有業務含義的自定義異常。推薦業界已定 義過的自定義異常,如: DAOException / ServiceException 等。


【參考】對於公司外的 http/api 開放接口必須使用“錯誤碼”;而應用內部推薦異常拋出; 跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess()方法、“錯誤碼”、“錯誤簡短訊息”;而應用內部推薦異常拋出。
說明:關於 RPC 方法返回方式使用 Result 方式的理由:

  • 使用拋異常返回方式,調用方如果沒有捕獲到就會產生運行時錯誤。
  • 如果不加棧訊息,只是 new 自定義異常,加入自己的理解的 error message,對於調用端解決問題 的幫助不會太多。如果加了棧訊息,在頻繁調用出錯的情況下,資料序列化和傳輸的性能損耗也是問題。

【參考】避免出現重複的程式碼(Don’t Repeat Yourself),即 DRY 原則。
說明:隨意複製和粘貼程式碼,必然會導致程式碼的重複,在以後需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個 public 方法,都需要進行數行相同的參數校驗操作,這個時候請抽取:
private boolean checkParam(DTO dto) {…}


心得

看完這篇「異常處理」後,其實滿多要注意和小心的,像是 finally 在使用要注意的事項,還有如果使用 try-catch 不要什麼都不處理;更重要的是 NPE 的處理。

結語

文章越看越多,技術越學越多,就會發現自己的不足;技術學到後面都會想要將基礎再重新在打得更加扎實。

以前在開發覺得理所當然的事情,例如:命名規則、命名規範,照著別人怎麼說就怎麼做的想法,並沒有好好去想為什麼要這樣設計和規範。
於是乎同事們推薦《阿里巴巴Java開發手冊》來做閱讀,書中提到種種規範《正確範例》、《錯誤範例》還有解釋定義說明;我相信在閱讀完這一系列後,一定會更加扎實且實在。

如對此書有興趣,建議去購買官方認證的書籍,給予官方支持。

註:如有侵權,通知即刪。


註:以上參考了
Alibaba-Java-Coding-Guidelines Github
Alibaba-Java-Coding-Guidelines English Version