9個處理Java異常的最佳實踐

Java中的異常處理不是一個容易的話題。初學者很難理解,即使是經驗豐富的開發人員也需要花費數小時來討論如何拋出或處理哪些異常。

這就是為什麼大多數開發團隊都有自己的規則來使用它們的原因。而且,如果您是團隊新手,那麼您可能會感到驚訝,這些規則與以前使用的規則有何不同。

儘管如此,大多數團隊還是採用了幾種最佳實踐。以下是9個最重要的信息,它們可以幫助您入門或改善異常處理。

1.在 Finally 塊中清理資源或使用Try-With-resource語句

您經常在try塊中使用資源,例如InputStream,然後需要關閉它。這些情況下的常見錯誤是在try塊的末尾關閉資源。

<code>public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
        // do NOT do this
        inputStream.close();
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}/<code>


問題在於,只要不拋出異常,此方法就可以很好地工作。try塊中的所有語句將被執行,並且資源將被關閉。

但是您添加try塊是有原因的。您調用一個或多個可能引發異常的方法,或者您自己引發異常。這意味著您可能未到達try塊的末尾。因此,您將不會關閉資源。

因此,您應該將所有清理代碼放入finally塊中,或使用try-with-resource語句。

使用 Finally Block

與try塊的最後幾行相反,finally塊始終執行。在成功執行try塊之後或在catch塊中處理了異常之後,就會發生這種情況。因此,可以確保清除所有打開的資源。

<code>public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}/<code>


Java 7的“嘗試使用資源”語句

另一個選擇是try-with-resource語句,我在Java異常處理簡介中對其進行了詳細說明。

如果您的資源實現了AutoCloseable (https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html)接口,則可以使用它。那就是大多數Java標準資源所做的。當您在try子句中打開資源時,將在try塊執行或發生異常後自動關閉資源。

<code>public void automaticallyCloseResource(){
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}/<code>


2.首選特定的異常

拋出的異常越具體越好。始終牢記,一個不知道您的代碼,或者可能幾個月後才知道您的同事,需要調用您的方法並處理異常。

因此,請確保為他們提供儘可能多的信息。這使您的API更易於理解。結果,您的方法的調用者將能夠更好地處理該異常,或者通過額外的check避免該異常。

因此,請始終嘗試查找最適合您的異常事件的類,例如,拋出NumberFormatException而不是IllegalArgumentException。並避免引發不確定的Exception。

<code>public void doNotDoThis() throws Exception {
    ...

}
public void doThis() throws NumberFormatException {
    ...
}/<code>


3. 用註釋來描述你特定的異常

每當在方法簽名中指定異常時,也應在Javadoc中對其進行記錄。這與以前的最佳實踐具有相同的目標:為呼叫者提供儘可能多的信息,以便他可以避免或處理異常。

因此,請確保在Javadoc中添加一個@throws聲明,並描述可能導致異常的情況。

<code>/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */
public void doSomething(String input) throws MyBusinessException {
    ...
}/<code>


4.拋出帶有描述信息的異常

最佳實踐背後的想法與前兩個類似。但是這一次,您沒有將信息提供給方法的調用者。每個必須瞭解該日誌文件或您的監視工具中報告該異常時發生的情況的人都可以閱讀該異常的消息。

因此,它應儘可能準確地描述問題,並提供最相關的信息以瞭解異常事件。

不要誤會我的意思;您不應該寫一段文字。但是您應該用1-2個簡短的句子來說明出現異常的原因。這可以幫助您的運營團隊瞭解問題的嚴重性,還可以使您更輕鬆地分析任何服務事件。

如果拋出特定異常,則其類名很可能已經描述了錯誤的種類。因此,您無需提供很多其他信息。一個很好的例子是NumberFormatException。當您以錯誤的格式提供String時,它將由類java.lang.Long的構造函數引發。

<code>try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
}/<code>


NumberFormatException類的名稱已經告訴您這類問題。它的消息僅需要提供引起問題的輸入字符串。如果異常類的名稱不那麼具有表現力,則需要在消息中提供所需的信息。


<code>17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"/<code>


5.首先捕獲特定的異常

大多數IDE都會為您提供最佳實踐幫助。當您嘗試首先捕獲不太具體的異常時,它們可能會報告無法訪問的代碼塊。

問題在於僅執行與異常匹配的第一個catch塊。因此,如果您首先捕獲一個IllegalArgumentException,則永遠不會到達應該處理更具體的NumberFormatException的catch塊,因為它是IllegalArgumentException的子類。

始終首先捕獲最具體的異常類,然後將不太具體的捕獲塊添加到列表的末尾。

您可以在以下代碼片段中看到此類try-catch語句的示例。第一個catch塊處理所有NumberFormatException,第二個所有IllegalArgumentException(不是NumberFormatException)。

<code>public void catchMostSpecificExceptionFirst() {
    try {
        doSomething("A message");
    } catch (NumberFormatException e) {
        log.error(e);
    } catch (IllegalArgumentException e) {
        log.error(e)
    }
}/<code>


6.不要 Catch Throwable

Throwable是所有異常和錯誤的超類。您可以在catch子句中使用它,但絕對不要這樣做!

如果在catch子句中使用Throwable,它將不僅捕獲所有異常,而且還會捕獲所有異常。它還會捕獲所有錯誤。JVM引發錯誤,以指示應用程序不打算處理的嚴重問題。典型的例子是OutOfMemoryError或StackOverflowError。兩者都是由應用程序無法控制的情況引起的,無法處理。

因此,最好不要捕獲Throwable,除非您完全確定自己處於特殊情況下,在這種情況下您能夠或被要求處理錯誤。

<code>public void doNotCatchThrowable() {
    try {
        // do something
    } catch (Throwable t) {
        // don't do this!
    }
}/<code>


7.不要忽略異常

您是否曾經分析過僅執行用例第一部分的錯誤報告?

這通常是由忽略的異常引起的。開發人員可能非常確定不會將其拋出,並添加了一個不會處理或記錄它的catch塊。而且,當您找到該塊時,您很可能甚至找到了著名的“這將永遠不會發生”註釋之一。

好吧,您可能正在分析一個不可能發生的問題。

<code>public void doNotIgnoreExceptions() {
    try {
        // do something
    } catch (NumberFormatException e) {
        // this will never happen
    }
}/<code>


因此,請不要忽略異常。您不知道將來的代碼將如何更改。有人可能會刪除阻止異常事件的驗證,而沒有意識到這會造成問題。或者,引發異常的代碼被更改,現在引發同一類的多個異常,並且調用代碼並不能阻止所有這些異常。

您至少應該寫一條日誌消息,告訴每個人剛剛發生的不可想象的事情,並且有人需要檢查它。

<code>public void logAnException() {
    try {
        // do something
    } catch (NumberFormatException e) {
        log.error("This should never happen: " + e);
    }
}/<code>


8.不要 Log 和 Throw

這可能是此列表中最常被忽略的最佳實踐。您可以找到許多代碼片段,甚至可以找到捕獲,記錄和重新拋出異常的庫。

<code>try { 

    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}/<code>


記錄發生的異常,然後將其重新拋出,以便調用者可以適當地處理它,這可能很直觀。但是它將為同一異常寫入多個錯誤消息。

<code>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)/<code>


附加消息也不會添加任何信息。如最佳做法4中所述,異常消息應描述異常事件。堆棧跟蹤會告訴您在哪個類,方法和行中引發了異常。

如果需要添加其他信息,則應捕獲異常並將其包裝在自定義異常中。但是請確保遵循最佳實踐9。

<code>public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}/<code>


因此,僅在要處理它時才捕獲異常。否則,請在方法簽名中指定它,然後讓調用者來處理它。

9.包裝異常而不丟掉原始的異常信息

有時最好捕獲一個標準異常並將其包裝到自定義異常中。這種例外的典型示例是特定於應用程序或框架的業務例外。這使您可以添加其他信息,還可以對異常類實施特殊處理。

執行此操作時,請確保將原始異常設置為原因。該異常類提供了接受一個特定的構造方法的Throwable作為參數。否則,您將丟失堆棧跟蹤和原始異常的消息,這將使得難以分析導致異常的異常事件。

<code>public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}/<code>


摘要

如您所見,拋出或捕獲異常時,您應該考慮很多不同的事情。他們中大多數人的目標是提高代碼的可讀性或API的可用性。

異常通常是同時存在的錯誤處理機制和通信介質。因此,您應該確保與同事討論要應用的最佳實踐和規則,以便每個人都能理解一般概念並以相同的方式使用它們。


9個處理Java異常的最佳實踐


分享到:


相關文章: