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

Java中的異常處理不是一個簡單的主題。初學者發現它很難理解,甚至有經驗的開發者也能夠花幾個小時討論如何以及應該拋出或處理哪些異常。html

這就是爲何大多數開發團隊都有本身的一套如何使用它們的規則。若是你是一個團隊的新手,你可能會驚訝這些規則與你以前使用的規則有多麼不一樣。java

儘管如此,大多數團隊都使用了幾種最佳實踐。如下是幫助你入門或改進異常處理的9個最重要的內容。api

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

在try塊中使用資源是很頻繁的,好比 InputStream ,以後須要關閉它。這些狀況中的一個常見錯誤是在try塊結束時關閉資源。架構

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);
    }
}

問題是隻要沒有拋出異常,這種方法彷佛徹底正常。try塊中的全部語句都將被執行,資源將被關閉。oracle

可是你添加了try塊是有緣由的。你調用一個或多個可能拋出異常的方法,或者你本身拋出異常。這意味着你可能沒法到達try塊的末尾。所以,你將不會關閉資源。app

所以,你應該將全部清理代碼放入finally塊或使用try-with-resource語句。框架

使用Finally塊

與try塊的最後幾行相比,finally塊始終執行。這能夠在成功執行try塊以後或在catch塊中處理異常以後發生。所以,你能夠確保清理全部已打開的資源。分佈式

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);
            }
        }
    }
}

Java 7的Try-With-Resource

另外一種選擇是try-with-resource語句,我在對此進行了更詳細的解釋。函數

若是資源實現 AutoCloseable 接口,則可使用它。這就是大多數Java標準資源所作的事情。當你在try子句中打開資源時,它將在try塊執行後自動關閉,或者處理異常。工具

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);
    }
}

2.特定異常

拋出的異常越具體越好。請記住,不明白你代碼的同事,或者你可能在幾個月後須要調用你的方法並處理異常。

所以,請務必提供儘量多的信息。這使你的API更易於理解。所以,你的方法的調用者將可以更好地處理異常或 經過額外的檢查來避免它 。

所以,老是嘗試找到最適合你的異常事件的類,例如拋出 NumberFormatException 而不是 IllegalArgumentException 。並避免拋出非特定的異常。

public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...
}

3.記錄你聲明的異常

不管什麼時候在方法簽名中,都應該 在Javadoc中記錄它 。這與之前的最佳實踐具備相同的目標:爲調用者提供儘量多的信息,以便他能夠避免或處理異常。

所以,請確保向Javadoc 添加@throws聲明並描述可能致使異常的狀況。

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

4.使用描述信息拋出異常

這種最佳實踐背後的想法相似於前兩種實踐。可是此次,你不向調用方提供有關方法的信息。每一個必須瞭解在日誌文件或監視工具中拋出異常時發生了什麼的人都會讀取異常的消息。

所以,它應該儘量準確地描述問題,並提供最相關的信息來理解異常事件。

別誤會個人意思; 你不該該寫一段文字。可是你應該用1-2個簡短的句子來解釋這個例外的緣由。這有助於你的運營團隊瞭解問題的嚴重性,還可讓你更輕鬆地分析任何服務事件。

若是拋出一個特定的異常,它的類名極可能已經描述了那種錯誤。所以,你無需提供大量其餘信息。一個很好的例子是NumberFormatException。它會被類java.lang.Long的構造函數拋出,當你以錯誤的格式提供String參數。

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

NumberFormatException類的名稱已經告訴你問題的類型。它的消息只須要提供致使問題的輸入字符串。若是異常類的名稱不具備表現力,則須要在消息中提供所需的信息。

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

5.優先捕獲最具體的異常

大多數IDE均可以幫助你實現這一最佳實踐。當你嘗試首先捕獲不太具體的異常時,它們提示沒法訪問的代碼塊。

問題是隻有匹配異常的第一個catch塊纔會被執行。所以,若是首先捕獲IllegalArgumentException,則永遠不會到達應該處理更具體的NumberFormatException的catch塊,由於它是IllegalArgumentException的子類。

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

你能夠在如下代碼段中看到此類try-catch語句的示例。第一個catch塊處理全部的NumberFormatException,第二個處理全部不是NumberFormatException的IllegalArgumentException 異常。

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

6.Don’t Catch Throwable

Throwable 是全部異常和錯誤的超類。你能夠在catch子句中使用它,但你永遠不該該這樣作!

若是在catch子句中使用Throwable,它不只會捕獲全部異常; 它還會捕獲全部錯誤。JVM拋出錯誤以指示應用程序沒法處理的嚴重問題。典型的例子是 OutOfMemoryError 或 StackOverflowError 。二者都是由應用程序沒法控制的狀況引發的,沒法處理。

因此,最好不要抓住Throwable,除非你徹底肯定你處於一個特殊狀況,你能夠或者須要處理錯誤。

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

7.Don’t Ignore Exceptions

你是否曾經分析過只有用例第一部分被執行的錯誤報告?

這一般是由忽略的異常引發的。開發人員可能很是肯定它永遠不會被拋出並添加了一個不處理或記錄它的catch塊。當你找到這個代碼塊時,你極可能甚至會發現一個著名的「This will never happen」的評論。

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

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

因此,請永遠不要忽視異常。你不知道代碼未來會如何變化。有人可能會刪除阻止異常事件的驗證而不會認識到這會產生問題。或者拋出異常的代碼會被更改,如今拋出同一個類的多個異常,而且調用代碼不會阻止全部這些異常。

你至少應該寫一條日誌消息,告訴你們難以想象的事情剛剛發生,並且有人須要檢查它。

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

8.Don’t Log and Throw

這多是此列表中最常被忽略的最佳作法。你能夠找到許多代碼片斷,甚至是catch,log和從新throw異常的庫。

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}

在發生異常時記錄異常可能會感受很直接,而後從新拋出它以便調用者能夠適當地處理它。但它會爲同一個異常寫出多條錯誤消息。

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中所述,異常消息應描述異常事件。堆棧跟蹤告訴你拋出異常的類,方法和行。

若是須要添加其餘信息,則應捕獲異常並將其包裝在自定義異常中。但請務必遵循最佳作法9。

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

所以,若是你想要處理它,只捕獲異常。不然,在方法簽名中指定它並讓調用者處理它。

9.在沒有消費的狀況下包裝異常

有時候捕獲標準異常並將其包裝成自定義異常會更好。此類異常的典型示例是應用程序或框架特定的業務異常。這容許你添加其餘信息,還能夠爲異常類實現特殊處理。

執行此操做時,請確保將原始異常設置爲cause。該異常類提供了接受一個特定的構造方法的Throwable做爲參數。不然,你將丟失原始異常的堆棧跟蹤和消息,這將致使難以分析致使異常的異常事件。

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

總結

正如所看到的,當你拋出或捕獲異常時,你應該考慮許多不一樣的事情。其中大多數都旨在提升代碼的可讀性或API的可用性。

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

歡迎學Java和大數據的朋友們加入java架構交流: 855835163
加羣連接:https://jq.qq.com/?_wv=1027&k=5dPqXGI 羣內提供免費的架構資料還有:Java工程化、高性能及分佈式、高性能、深刻淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階乾貨的免費直播講解  能夠進來一塊兒學習交流哦

相關文章
相關標籤/搜索