JAVA 中異常處理的最佳實踐

前言

異常處理的問題之一是知道何時以及如何去使用它。我會討論一些異常處理的最佳實踐,也會總結最近在異常處理上的一些爭論。

作爲程序員,我們想要寫高質量的能夠解決問題的代碼。但是,異常經常是伴隨着代碼產生的副作用。沒有人喜歡副作用,因此我們會試圖用自己的方式來解決這個問題。我看過不少的程序用下面的方法應對異常:

上面這段代碼的問題在哪裏?

一旦一個異常被拋出之後,正常的執行流程會停止並且將控制交給捕捉塊。捕捉塊捕獲異常,然後只是把它的信息打印了一下。之後程序正常運行,就像沒有任何事情發生一樣。

那下面的這種方法呢?

這是一個空方法,裏面沒有任何的代碼。爲什麼一個空方法能夠拋出異常?JAVA並不阻止你這麼做。最近,我遇到了一些和這個很相似的代碼,明明代碼塊中沒有拋出異常的語句,卻在方法聲明中拋出異常。當我問開發人員爲什麼這麼做,他會回答“我知道這樣會影響API,但是我之前就這麼做的而且效果還不錯”。

C 社區花了好久才決定如何使用異常。這場爭論也在JAVA社區產生了。我看到不少JAVA開發人員艱難的使用異常。如果不能夠正確使用的話,異常會影響程序的性能,因爲它需要使用內存和CPU來創建,拋出以及捕獲。如果過度使用的話,會使得代碼難以閱讀,並且影響API的使用人員。我們都知道這將會帶來代碼漏洞以及壞味道。客戶端代碼常會通過忽略這個異常或是直接將其拋出來避開這個問題,就像之前的兩個例子那樣。

小編是一個有着5年工作經驗的java程序員,對於java,自己有做資料的整合,一個完整學習java的路線,學習資料和工具,相信這裏有很多學習java的小夥伴,我創立了一個2000人學習扣羣,479121291。每晚都有java的直播課程。無論是初級還是進階的小夥伴小編我都歡迎!

異常的本質

從廣義的角度來說,一共有三種不同的場景會導致異常的產生:

編程錯誤導致的異常:這一類的異常是因爲不恰當的編程帶來的(比如 , )。客戶端通常無法對這些錯誤採取任何措施

客戶端代碼的錯誤:客戶端代碼在API允許的範圍之外使用API,從而違背了合約。客戶端可以通過異常中提供的有用信息,採用一些替代方法。比如,當解析格式不正確的XML文件時,會拋出異常。這個異常中包含導致該錯誤發生的XML內容的具體位置。客戶端可以通過這些信息採取回覆措施。

資源失效導致的異常:比如系統內存不足或是網絡連接失敗。客戶端面對資源失效的迴應是要根據上下文來決定的。客戶端可以在一段時間之後試着重新連接或是記錄資源失效日誌然後暫停應用程序。

JAVA異常類型

JAVA定義了兩種異常:

需檢查的異常:從 類繼承的異常都是需檢查異常。客戶端需要處理API拋出的這一類異常,通過try-catch或是繼續拋出。

無需檢查的異常: 也是 的子類。但是,繼承了 的類受到了特殊的待遇。客戶端代碼無需專門處理這一類異常。

下圖展示了 的繼承樹:

上圖中, 繼承自 ,因此它也是一個無需檢查的異常。

我看到過大量使用需檢查異常只在極少數時候使用無需檢查異常的。最近,JAVA社區在需檢查異常的真正價值上爆發了熱烈的討論。這場辯論源於JAVA是第一個包含需檢查異常的主流OO框架。C 和C#根本沒有需檢查異常。這些語言中所有的異常都是無需檢查的。

從低層拋出的需檢查異常強制要求調用方捕獲或是拋出該異常。如果客戶端不能有效的處理該異常,API和客戶端之間的異常協議將會帶來極大的負擔。客戶端的開發人員可能會通過將異常抑制在一個空的捕獲塊中或是直接拋出它。從而又將這個負擔交給了客戶端的調用方。

還有人指責需檢查異常會破壞封裝,看下面這段代碼:

方法拋出了兩個需檢查異常。調用這個方法的客戶端必須明確的處理這兩種具體的異常,即使它們並不清楚 內究竟是哪個文件訪問或是數據庫訪問失敗了,而且它們也沒有提供文件系統或是數據庫的邏輯。因此,這樣的異常處理導致方法和調用者之前出現了不當的強耦合。

設計API的最佳實踐

在討論了這些之後,我們可以來探討一下如何設計一個正確拋出異常的良好的API。

1.在選擇拋出需確定異常或是無需確定異常時,問自己這樣的一個問題:客戶端代碼在遇到異常時會進行怎樣的處理?

如果客戶端能夠採取措施從這個異常中恢復過來,那就選擇需確定異常。如果客戶端不能採取有效的措施,就選擇無需確定異常。有效的措施是指從異常中恢復的措施,而不僅僅是記錄錯誤日誌。

除此以外,儘量選擇無需確定的異常:它的優點在於不會強迫客戶端顯式地處理這種異常。它會冒泡到任何你想捕獲它的地方。JAVA API提供了許多無需檢查的異常如 , 和 。我傾向於使用JAVA提供的標準的異常,儘量不去創建自己的異常。

2.保留封裝

永遠不要將特定於實現的異常傳遞到更高層。比如,不要將數據層的 傳遞出去。業務層不需要了解 。你有兩個選擇:

將 轉換爲另一個需檢查異常,如果客戶代碼需要從異常中恢復。

將 轉換爲無需檢查異常,如果客戶端代碼無法對其進行處理。

大多數時候,客戶代碼無法解決 。這時候就將其轉化爲無需檢查的異常。

這裏的catch塊並沒有做任何事情。不如通過如下的方式解決它:

這裏將 轉化爲了 。如果 出現了,catch塊就會拋出一個運行時異常。當前執行的線程將會停止並報告該異常。但是,該異常並沒有影響到我的業務邏輯模塊,它無需進行異常處理,更何況它根本無法對 進行任何操作。如果我的catch塊需要根異常原因,可以使用 方法。

如果你確信業務層可以採取補救措施,你可以將其轉化爲一個更有意義的無需檢查異常。但是我覺得拋出RuntimeException足以適用大多數的場景。

3.當無法提供更加有用信息時,不要自定義異常

下面這段代碼有什麼問題?

它沒有給客戶端代碼提供任何有用的信息,除了一個稍微具有含義的命名。不要忘了 類和別的類一樣,在裏面你可以添加一下方法供客戶端調用,獲得有用的信息。

新版本的異常提供了兩個有用的方法: ,它會返回請求的名字,和 ,它會返回一組相近的可用的用戶名。客戶端可以使用這些方法來獲取有用的信息。但是如果你不準備添加這些額外的信息,那就拋出一個標準的異常即可。

如果你覺得客戶端代碼在記錄日誌之外對這個異常不能進行任何操作,那麼最好拋出無需檢查異常:

除此以外,你還可以提供一個方法來檢查用戶名是否已經被使用。

4.文檔化異常

你可以使用Javadoc的 標記來記錄需檢查異常和無需檢查異常。但是,我傾向於寫單元測試來文檔化異常。單元測試允許我在使用中查看異常,並且作爲一個可以被執行的文檔來使用。無論你採用哪種方法,儘量使你的客戶端代碼瞭解你的API會拋出的異常。這裏提供了 的單元測試。

上面這段代碼在調用 應當拋出 。如果沒有拋出該異常,則會執行 顯式的說明該測試失敗了。通過爲異常編寫測試,你不僅能記錄異常如何觸發,而且使你的代碼在經過這些測試後更加健壯。

使用異常的最佳實踐

1.自覺清理資源

如果你在使用如數據庫連接或是網絡連接之類的資源,要確保你及時的清理這些資源。如果你調用的API僅僅出發了無需檢查異常,你仍然需要在使用後主動清理。使用 塊。

類關閉 連接。這裏的重點在於在 塊中關閉連接,無論是否出現了異常。

2.永遠不要使用異常來控制流

生成棧追蹤的代價很昂貴,它的價值在於debug過程中使用。在一個流程控制中,棧追蹤應當被忽視,因爲客戶端只想知道如何進行。

在下面的代碼中, 被用來進行流程控制:

通過無限循環來增加計數,直到拋出異常。這種方式使得代碼難以閱讀,而且影響代碼性能。只在出現異常的場景拋出異常。

3.不要無視或是壓制異常

當API的方法會拋出異常的時候,它在提醒你應當採取一些措施。如果需檢查異常沒有任何意義,那就乾脆將其轉化爲無需檢查異常再重新拋出。不要單純的用catch捕獲它然後繼續執行,彷彿什麼都沒有發生一樣。

4.不要捕獲最高層異常

繼承 的異常同樣是 的子類。捕獲 的同時,也捕獲了運行時異常:

5.只記錄異常一次

將同一個異常多次記入日誌會使得檢查追蹤棧的開發人員感到困惑,不知道何處是報錯的根源。所以只記錄一次。

覺得本文對你有幫助?請分享給更多人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章