異常處理最佳實踐

Juven發表於2011-10-26

注:本文原載於http://www.juvenxu.com/2011/03/30/exception-handling-best-practices/

Exception

作為一個已經寫了近5年Java程式碼的程式設計師,我直到最近才算是基本明白了異常應該怎麼用,這真是令人汗顏。事情是這樣的,上週,和往常一樣,我在開發一個很平常的應用,並且不得不面對各種各樣的異常,比如常見的IOException,或者用到個第三方類庫可能會給你返回ThirdPartyException,還有,我自己也會定義異常,姑且叫它MyOwnException。我是使用分層的架構寫程式碼的,比如有個REST層,有個領域模型層,有持久化層,這本沒什麼問題,可當我發現一個介面要throws三四個或者更多的異常的時候,就覺得蛋疼了,這不僅看起來噁心,如呼叫者如何處理它們似乎也是個問題。這不是第一次,其實之前蛋疼過很多次了,我一直閱讀各種OO設計相關書籍,以理解如何組織各種類和物件,可我還真沒仔細考慮過如何組織異常。

好吧,為了以後不再蛋疼,我得弄清楚這個異常的用法。我希望能夠明白如何組織異常才能使其變得整潔,而不是肆無忌憚地汙染我精心設計的介面。

我翻閱了相關書籍,仔細檢視了它們關於異常的描述,包括

  • Implementation Patterns — Kent Beck
  • Clean Code: A Handbook of Agile Software Craftsmanship — Robert C. Martin
  • Effective Java (2ed) — Joshua Bloch
  • Robust Java: Exception Handling, Testing, and Debugging — Stephen Stelting

邊閱讀邊思考,最終基本理解了異常處理的最佳實踐。值得一提的是,這幾本書中 Joshua Bloch 的描述相比最為全面和深刻,Kent Beck 的最簡單粗糙,Robert C. Martin 一書的那一章其實是 Michael Feathers 寫的,也只能算是一般,Stephen Stelting 的描述看起來很全面,可太理論了,例項太少,沒有說服力。

那異常處理的最佳實踐到底是什麼?首先要理解的是,異常到底是什麼,我覺得異常分兩類。

第一是業務異常,就是處理業務的時候80%的時候是沒問題的,但可能有20%的時候事情沒有按理想的方向發展。例如註冊使用者的時候,正常情況是註冊成功,但可能使用者提交請求的時候,系統發現使用者名稱已經被別人註冊了,這是就可以丟擲一個UserAlreadyExistsException。

第二是系統異常,系統異常與具體業務流程沒有直接的關係,例如程式設計錯誤導致的NullPointExcpetion,還有壞境問題,例如磁碟損壞或者網路連線不穩定造成了IOException。

先談談業務異常,業務異常應該是介面的一部分,該介面的使用者應該能夠處理該異常。也就是說,在實現介面之前,就應該定義清楚這個介面會丟擲怎樣的異常,例如註冊使用者這個介面就應該明確定義會丟擲UserAlreadyExistsException,並輔以必要文件,那呼叫者就知道怎麼去處理。如果你在介面宣告異常,那你一定要確保介面的呼叫者能夠處理之。例如對應於 UserAlreadyExistsException,使用者介面可以提示使用者該ID已存在,那使用者可以使用另外的ID來註冊。注意,處理異常不是簡單的catch,然後隨便列印點日誌那麼簡單,業務異常的處理應當遵照業務流程。

系統異常由於沒有實際業務意義,呼叫者往往是不知道如何處理的。例如註冊使用者的時候收到一個IOException,你只會一頭霧水。那系統異常應該如何處理呢?有這麼幾個選擇:

  1. 能在底層處理的就本地處理掉,例如網路連線超時異常,可以編寫程式碼嘗試再次連線。
  2. 能翻譯為業務異常的就翻譯,有時候你可能發現某個IOException對應有實際的業務意義,但不要直接丟擲,而是應該Exception Chaining技術來翻譯,例如 throw new MyOwnException(“sorry, …”, ioe),這樣既不至於丟失原始資訊,業務介面也更容易理解。
  3. 實在無法處理的,就轉換為RuntimeException,反正呼叫者也不知道怎麼處理,那在介面中宣告是沒有意義的,只會造成呼叫者困惑並帶來負擔。這裡同樣應該使用Exception Chaining以避免資訊丟失。我的實際經驗告訴我,很多人完全都不考慮使用RuntimeException,這真是莫大的浪費。

有時候業務異常和系統異常的角色會相互轉換,例如 FileNotFoundException 對於檔案處理這個領域來說是業務異常,可對於我的應用來說,可能只是一個系統異常。

上述是我認為異常處理最核心最有用的實踐,除此之外還有不少前人總結的經驗,包括:

  • 記錄異常,但只記錄一次。(不要丟失歷史)
  • 不要返回及傳Null值。(避免NullPointException和不必要的檢查)
  • 儘量重用JDK自帶的異常。(以降低學習成本)
  • 在自定義異常中儲存異常相關資訊欄位。(以方便日後處理)
  • 永遠不要直接忽略異常。(不要丟失歷史)
  • 為異常宣告寫Javadoc。(對你的使用者有好些)
  • 不要 throws 或者 catch 頂層的 Exception 類。(天知道Exception對應什麼業務資訊)

其實當我讀畢前面提到的幾本書,然後再Google相關文件的時候,就發現已經有人基本總結過這些內容了:Best Practices for Exception Handling, by Gunjan Doshi, 文章是2003年寫的,但一點也不過時。相比之下我這篇文章有汙染搜尋引擎之嫌,不管怎樣,這算是自我的一個總結,對英文不好的人應該也有些價值。

相關文章