Java異常處理的9個最佳實踐,看看自己是不是都用過?
原文 | dzone.com/articles/9-…
作者 | Thorben Janssen
翻譯 | geekymv
無論你是初學(xué)者還是經(jīng)驗豐富的開發(fā)人員,對于你和你的團(tuán)隊來說,提高異常處理的能力可以更好的解決問題。
Java中的異常處理并不是一件容易的事,初學(xué)者會覺得很難理解,即使是經(jīng)驗豐富的開發(fā)人員也可能需要花費幾個小時來討論應(yīng)該如何拋出或處理哪些異常。
這也是為什么大多數(shù)開發(fā)團(tuán)隊對于如何使用它們有自己的一套規(guī)則。如果你剛加入一個團(tuán)隊,你可能會驚訝這些規(guī)則與你之前使用過的規(guī)則是多么的不同。
盡管如此,依然有一些最佳實踐在大多數(shù)團(tuán)隊中被使用。以下9個最重要的方法,可以幫助你開始或提高異常處理。
1、在 finally 代碼塊中清理資源或使用 try-with-resource 語句
你經(jīng)常會在try代碼塊中使用一個資源,比如 InputStream,需要在之后關(guān)閉它。在這種情況下的一個常見錯誤是在try塊的末尾關(guān)閉資源。

問題是,只要沒有拋出異常這種方式可以很好的工作。try 代碼塊中的語句將被執(zhí)行,并且資源將被關(guān)閉。
但是你添加 try 代碼塊是有原因的,你調(diào)用一個或多個可能拋出異常的方法,或者可能是你自己拋出異常,這意味著你可能未到達(dá)try代碼塊的尾部,最終,你無法關(guān)閉資源。
因此,你應(yīng)該把所有清理代碼放在 finally 代碼塊中,或者使用 try-with-resource 語句。
使用 finally 代碼塊
與 try 代碼塊最后幾行不同,finally 代碼塊總是被執(zhí)行。這種情況發(fā)生在 try 代碼塊成功執(zhí)行之后,或者在catch 代碼塊中處理異常之后。因此,你可以確保清理了所有打開的資源。

Java7 中的 Try-With-Resource 語句
另一種方式是 try-with-resource 語句,我在介紹Java異常處理一文中有更詳細(xì)的說明。
如果你的資源實現(xiàn)了 AutoCloseable 接口,就可以使用它。這是大多數(shù) Java 標(biāo)準(zhǔn)資源所做的。當(dāng)你在 try 子句中打開資源時,它將在try 代碼塊執(zhí)行或者發(fā)生異常后自動關(guān)閉。

2、首選具體的異常
拋出的異常越具體越好。請記住,一個不知道你的代碼的同事,也可能是幾個月以后的你,需要調(diào)用你的方法并處理異常。
因此,確保提供給他們盡可能多的信息。這使你的API更容易理解。最終,方法的調(diào)用者將能夠更好地處理異?;蛲ㄟ^額外的檢查來避免異常。
因此,總是試著找到最合適你的異常事件的類,例如,拋出 NumberFormatException 而不是 IllegalArgumentException。避免拋出一個不具體的異常。

3、為指定的異常編寫文檔
無論什么時候你在方法簽名上指定一個異常時,你都應(yīng)該在你的Javadoc中為其編寫文檔。這與之前的最佳實踐有同樣的目標(biāo):提供給調(diào)用者盡可能多的信息,以便他可以避免或者處理異常。
因此,確保在你的Javadoc中增加@throws 聲明,并且描述可能造成異常的情況。

4、拋出帶有描述性信息的異常
這個最佳實踐背后的思想和之前兩個類似,不同的是,你不用將信息提供給方法的調(diào)用者。每個需要了解記錄在日志文件或監(jiān)控工具中異常信息的人,都可以閱讀該異常信息。
因此,應(yīng)該盡可能準(zhǔn)確的描述問題,并且提供最相關(guān)的信息以了解異常事件。
不要誤會我的意思,你不應(yīng)該寫一個文本段落,而是應(yīng)該用1-2兩個短句解釋異常的原因。這樣可以幫助你的運維團(tuán)隊了解問題的嚴(yán)重性,也可以使你更容易分析任何服務(wù)事件。
如果拋出一個具體的異常,它的類名將最可能已經(jīng)描述了錯誤的種類。因此,你不需要提供很多額外的信息。一個好的例子是 NumberFormatException。當(dāng)你提供一個錯誤的字符串格式時,將由 java.lang.Long 類的構(gòu)造方法拋出 NumberFormatException 異常。

NumberFormatException?類的名字已經(jīng)告訴你問題的種類。它的信息僅僅需要提供導(dǎo)致問題的輸入字符串。如果異常類的名字不那么具有表現(xiàn)力,則需要在消息中提供必要的信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
譯者注:可見,給類取個好名字多么重要。
5、優(yōu)先捕獲最具體的異常
大多數(shù) IDE 都可以幫助你實現(xiàn)這個最佳實踐。 當(dāng)你嘗試捕獲不太具體的異常時,它們會報告一個不可到達(dá)的代碼塊。
問題在于,只有第一個與異常匹配的 catch 代碼塊才會被執(zhí)行。因此,如果你首先捕獲一個 IllegalArgumentException 異常,你將不能到達(dá)應(yīng)該處理更具體的 NumberFormatException 異常的 catch 代碼塊。因為它是 IllegalArgumentException 類的子類。
總是首先捕獲最具體的異常,然后將不太具體的 catch 代碼塊添加到列表的尾部。
在下面的代碼片段中,你可以看到 try-catch 語句的例子,第一個 catch 代碼塊處理所有 NumberFormatException 異常,并且第二個 catch 代碼塊處理所有不屬于 NumberFormatException 的 IllegalArgumentException 異常。

6、不要捕獲 Throwable
Throwable 是所有 Exception 和 Error 類的父類。你可以在 catch 語句中使用它,但是你絕對不要這樣做!
如果你在 catch 語句中使用 Throwable,它不僅捕獲所有的 Exception,還將捕獲所有的 Error。
Error 是被 JVM 拋出的,它表示不能被應(yīng)用程序處理的嚴(yán)重問題。典型的例子是 OutOfMemoryError 或 StackOverflowError,都是由應(yīng)用程序無法控制的情況引起的,并且無法處理。
因此,最好不要捕獲 Throwable,除非你絕對確信自己處于能夠或需要處理錯誤的特殊情況下。

7、不要忽略異常
你是否曾經(jīng)分析過一個bug報告,其中只執(zhí)行了用例的第一部分?
這通常是由忽略異常引起的,開發(fā)者可能很確信它從不會拋出,并且添加了不處理或不打印日志的 catch 代碼塊,當(dāng)你找到這個代碼塊的時候,你甚至可能發(fā)現(xiàn)一個著名的注釋“This will never happen”。

因此,請不要忽略異常。你不知道代碼在未來將如何被改變。有人可能會刪除阻止異常事件的驗證,而沒有意識到這會造成問題?;驋伋霎惓5拇a被更改,現(xiàn)在拋出同一個類的多個異常,并且調(diào)用代碼并不能阻止所有這些異常。
你至少應(yīng)該寫一條日志信息,告訴每個人意想不到的事情剛剛發(fā)生了,需要有人來檢查它。

8、不要打印日志的同時拋出異常
這可能是列表中最經(jīng)常被忽略的最佳實踐。你可以在許多代碼段甚至庫中發(fā)現(xiàn)異常被捕獲、打印日志、并重新拋出。

記錄發(fā)生的異常,然后將其重新拋出,以便調(diào)用者可以適當(dāng)?shù)奶幚硭?,這可能會很直觀。但是它將為同一異常寫入多個錯誤信息。
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.(Long.java:965) at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63) at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
額外的消息也沒有增加任何信息。正如最佳實踐4中所述,異常信息應(yīng)該描述異常事件。堆棧跟蹤會告訴你在哪個類,方法和行中引發(fā)了異常。
如果你需要增加額外的信息,你應(yīng)該捕獲異常并且將其包裝在自定義異常中,但是請確保遵循最佳實踐9。

9、包裝異常而不使用它
有時最好是捕獲一個異常并將其包裝到自定義異常中。這樣異常的典型例子是應(yīng)用程序或框架的具體業(yè)務(wù)異常。這使你可以增加額外的信息,并且還可以對異常類實現(xiàn)特殊處理。
當(dāng)你這樣做的時候,確保將原始異常設(shè)置為原因(cause)。Exception 類提供了接收 Throwable 參數(shù)的具體構(gòu)造方法。否則,你會丟失堆棧跟蹤和原始異常的信息,這將使分析導(dǎo)致異常的事件變得困難。

總結(jié)
如你所見,當(dāng)你拋出或捕獲異常的時候,你應(yīng)該考慮很多不同的事情。它們中大多數(shù)的目標(biāo)是提高代碼的可讀性或API的可用性。
異常通常是一種錯誤處理機(jī)制,同時也是一種通信機(jī)制。因此,你應(yīng)該確保與你的同事討論你想要應(yīng)用的最佳實踐和規(guī)則,以便每個人都理解一般的概念,并以相同的方式使用它們。
了解更多,請點擊:https://www.bilibili.com/video/BV137411V7Y1
作者:Geek_ymv
鏈接:https://juejin.cn/post/6909838417278959630
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。