Java 處理 Exception 的 9 個最佳實踐!



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

譯者:颯然Hangmysql

譯文:http://www.rowkey.me/blog/2017/09/17/java-exception/web



在Java中處理異常並非一個簡單的事情。不只僅初學者很難理解,即便一些有經驗的開發者也須要花費不少時間來思考如何處理異常,包括須要處理哪些異常,怎樣處理等等。這也是絕大多數開發團隊都會制定一些規則來規範對異常的處理的緣由。而團隊之間的這些規範每每是大相徑庭的。spring

本文給出幾個被不少團隊使用的異常處理最佳實踐。sql

1. 在Finally塊中清理資源或者使用try-with-resource語句

當使用相似InputStream這種須要使用後關閉的資源時,一個常見的錯誤就是在try塊的最後關閉資源。api

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

上述代碼在沒有任何exception的時候運行是沒有問題的。可是當try塊中的語句拋出異常或者本身實現的代碼拋出異常,那麼就不會執行最後的關閉語句,從而資源也沒法釋放。緩存

合理的作法則是將全部清理的代碼都放到finally塊中或者使用try-with-resource語句。微信

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

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. 指定具體的異常

儘量的使用最具體的異常來聲明方法,這樣才能使得代碼更容易理解。mybatis

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

如上,NumberFormatException字面上便可以看出是數字格式化錯誤。併發

3. 對異常進行文檔說明

當在方法上聲明拋出異常時,也須要進行文檔說明。和前面的一點同樣,都是爲了給調用者提供儘量多的信息,從而能夠更好地避免/處理異常。異常處理的 10 個最佳實踐,這篇也推薦看下。

在Javadoc中加入throws聲明,而且描述拋出異常的場景。

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

4. 拋出異常的時候包含描述信息

在拋出異常時,須要儘量精確地描述問題和相關信息,這樣不管是打印到日誌中仍是監控工具中,都可以更容易被人閱讀,從而能夠更好地定位具體錯誤信息、錯誤的嚴重程度等。

但這裏並非說要對錯誤信息長篇大論,由於原本Exception的類名就可以反映錯誤的緣由,所以只須要用一到兩句話描述便可。

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

NumberFormatException即告訴了這個異常是格式化錯誤,異常的額外信息只須要提供這個錯誤字符串便可。當異常的名稱不夠明顯的時候,則須要提供儘量具體的錯誤信息。

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

如今不少IDE都能智能提示這個最佳實踐,當你試圖首先捕獲最籠統的異常時,會提示不能達到的代碼。當有多個catch塊中,按照捕獲順序只有第一個匹配到的catch塊才能執行。所以,若是先捕獲IllegalArgumentException,那麼則沒法運行到對NumberFormatException的捕獲。

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

6. 不要捕獲Throwable

Throwable是全部異常和錯誤的父類。你能夠在catch語句中捕獲,可是永遠不要這麼作。若是catch了throwable,那麼不只僅會捕獲全部exception,還會捕獲error。而error是代表沒法恢復的jvm錯誤。所以除非絕對確定可以處理或者被要求處理error,不要捕獲throwable。

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

7. 不要忽略異常

不少時候,開發者頗有自信不會拋出異常,所以寫了一個catch塊,可是沒有作任何處理或者記錄日誌。

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. 不要記錄並拋出異常

能夠發現不少代碼甚至類庫中都會有捕獲異常、記錄日誌並再次拋出的邏輯。以下:

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)

如上所示,後面的日誌也沒有附加更有用的信息。若是想要提供更加有用的信息,那麼能夠將異常包裝爲自定義異常。

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(Exception有構造方法能夠傳入cause)。不然,丟失了原始的異常信息會讓錯誤的分析變得困難。

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

總結

綜上可知,當拋出或者捕獲異常時,有不少不同的東西須要考慮。其中的許多點都是爲了提高代碼的可閱讀性或者api的可用性。異常不只僅是一個錯誤控制機制,也是一個溝通媒介,所以與你的協做者討論這些最佳實踐並制定一些規範可以讓每一個人都理解相關的通用概念而且可以按照一樣的方式使用它們。


     

更多好文章

  1. Java高併發系列(共34篇)

  2. MySql高手系列(共27篇)

  3. Maven高手系列(共10篇)

  4. Mybatis系列(共12篇)

  5. 聊聊db和緩存一致性常見的實現方式

  6. 接口冪等性這麼重要,它是什麼?怎麼實現?

  7. 泛型,有點難度,會讓不少人懵逼,那是由於你沒有看這篇文章!



世界上最好的關係是相互成就,點贊轉發 感恩開心😃




路人甲java

▲長按圖片識別二維碼關注


路人甲Java:工做10年的前阿里P7,全部文章以系列的方式呈現,帶領你們成爲java高手,目前已出:java高併發系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,正在連載springcloud系列,歡迎關注!


本文分享自微信公衆號 - 路人甲Java(javacode2018)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索