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
,就意味着程序發生了嚴重的錯誤,應用程序本身已無法自行處理,比如我們常見的OutOfMemoryError
和StackOverflowError
,此種類型錯誤的發生意味者已經超出應用程序本身所能處理的範疇,所以,不要在代碼中試圖捕獲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