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

1、確保在Finally程序塊中完成資源釋放或者使用Try-With語句

比如對於InputStream,當我們使用完畢,我們要確保資源被正確關閉,比如下面我們常見的錯誤用法,不要在try模塊中關閉資源,因爲一旦try語句塊中的其他方法發生異常,很有可能無法執行到inputStream.close()方法的。

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

推薦做法,使用Finally語句塊

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 語句來確保資源文件可以正常關閉

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、拋出異常越準確越好


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

3、對所拋出的異常進行必要的文字說明

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

4、拋出的異常應包含有描述性的信息

比如,對於NumberFormatException這類異常,異常本身已經告訴你錯誤類型,所以只需要傳入的轉換字符串,然而,對於一些無法幫助我們定位錯誤信息的異常,必要的描述性信息還是很有必要的。

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

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

5、在最開始捕獲絕大部分特定異常

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

6、不要去試圖捕獲Throwable

注意,Throwable 是所有異常(Exception)和錯誤(Error)的父類,如果在try-catch語句中使用了Throwable,你不僅捕獲了所有的異常,還包括所有的錯誤error,一旦JVM拋出了Error,就意味着程序發生了嚴重的錯誤,應用程序本身已無法自行處理,比如我們常見的OutOfMemoryErrorStackOverflowError,此種類型錯誤的發生意味者已經超出應用程序本身所能處理的範疇,所以,不要在代碼中試圖捕獲Throwable,除非你十二分確認異常情況,並且有能力自行處理。

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

7、不要忽略異常

某些編程人員可能會盲目篤信彼時彼地某個代碼段永遠都不會發生異常,並且很自信的加上了一個catch代碼塊,但在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、不要同時日誌記錄並拋出異常

這可能是我們最容易忽視的一種代碼實踐,即:不要同時進行日誌記錄並拋出異常。

下面是我們最常見的一種異常代碼書寫方式,這也是我們異常處理的最常見方式,首先記錄日誌,然後重新拋出異常,但其實這並不是一種好的代碼實踐:

ry {
    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、對異常自定義封裝時,切記不要掉任何異常信息

有時候,我們常常需要將異常封裝爲自定義異常,但切記,封裝時不要喫掉任何標準的異常信息,切記保留異常最初發生的原因,我們可以通過Exception類提供的特定的可以接受Throwable類型的構造方法來實現自定義異常,否則,我們將丟失異常發生堆棧信息,進而導致我們分析問題困難。

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

英文原文

https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java

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