Java的異常處理不是一個(gè)簡(jiǎn)單的話題。 初學(xué)者很難理解析恋,甚至經(jīng)驗(yàn)豐富的開發(fā)人員也可能花幾個(gè)小時(shí)討論何時(shí)應(yīng)該拋出異郴或處理異常。
這就是為什么大多數(shù)開發(fā)團(tuán)隊(duì)都有自己使用異常的規(guī)則系任。 如果你是一個(gè)新手恳蹲,你可能會(huì)驚訝于這些規(guī)則與你之前使用過的規(guī)則完全不同虐块。
無論規(guī)則如何不同,或多或少都遵循以下規(guī)則嘉蕾。 以下是幫助你入門或改進(jìn)異常處理的9個(gè)最佳實(shí)踐贺奠。
1.在finally塊中的回收資源或使用Try-With-Resource語(yǔ)句
經(jīng)常在try中使用資源,比如InputStream 错忱,之后需要關(guān)閉它儡率。 這些情況下一個(gè)常見錯(cuò)誤是在try塊結(jié)束時(shí)關(guān)閉資源。
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塊中的所有語(yǔ)句都將被執(zhí)行,資源將被關(guān)閉玖媚。
如果有異常被拋出箕肃。 這意味著你可能無法到達(dá)try塊的末尾。 因此今魔,資源無法被關(guān)閉勺像。
所以,你應(yīng)該將所有清理代碼放入finally塊或使用try-with-resource語(yǔ)句错森。
使用Finally塊
與try塊的最后幾行相比吟宦,finally塊始終被執(zhí)行。 這可以在成功執(zhí)行try塊之后或在catch塊中處理異常之后發(fā)生涩维。 因此殃姓,所有已打開的資源可以確保被清理。
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語(yǔ)句
另一種選擇是使用try-with-resource語(yǔ)句
前提是資源實(shí)現(xiàn)了AutoCloseable接口瓦阐, 大多數(shù)Java標(biāo)準(zhǔn)資源都實(shí)現(xiàn)了AutoCloseable接口蜗侈。 你在try子句中打開資源后,它將在try塊執(zhí)行后自動(dòng)關(guān)閉睡蟋。
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.拋出具體的異常而不是通用的異常
異常越具體越好踏幻。 請(qǐng)記住,不熟悉代碼的同事戳杀,或者你可能在幾個(gè)月后調(diào)用你的方法并處理異常该面。
因此,請(qǐng)務(wù)必盡可能多地提供信息信卡。 這使您的API更容易理解隔缀。 您的方法的調(diào)用者將能夠更好地處理異常或通過額外的檢查來避免它 坐求。
總是嘗試找到最適合您的異常事件的類蚕泽,例如拋出NumberFormatException而不是IllegalArgumentException 。 并避免拋出通用的異常 桥嗤。
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
3.在文檔中說明你的異常
只要在方法簽名中指定了異常 须妻,就應(yīng)該在Javadoc中記錄它 。 這與前面的最佳實(shí)踐具有相同的目的:為調(diào)用者提供盡可能多的信息泛领,以便他可以避免或處理異常荒吏。
因此,請(qǐng)確保向Javadoc添加@throws聲明并描述可能導(dǎo)致異常的情況渊鞋。
/**
* This method does something extremely useful ...
*
* @param input
* @throws MyBusinessException if ... happens
*/
public void doSomething(String input) throws MyBusinessException {
...
}
4.使用描述性信息來定義異常類
盡可能準(zhǔn)確地描述問題绰更,并提供最相關(guān)的信息來理解異常事件。
別誤會(huì)我的意思; 你不應(yīng)該寫一段文字锡宋。 但你應(yīng)該用1-2個(gè)短句來解釋異常的原因儡湾。 這有助于您的運(yùn)營(yíng)團(tuán)隊(duì)了解問題的嚴(yán)重性,還可以讓您更輕松地分析任何服務(wù)事件执俩。
如果你拋出一個(gè)特定的異常徐钠,它的類名很已經(jīng)描述了具體錯(cuò)誤。 你就無需提供大量其他信息役首。 一個(gè)很好的例子是NumberFormatException 尝丐。 當(dāng)你的參數(shù)是一個(gè)字符串而不是整數(shù)的時(shí)候,它會(huì)被類java.lang.Long的構(gòu)造函數(shù)拋出衡奥。
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
NumberFormatException類的名稱已經(jīng)告訴你錯(cuò)誤的原因爹袁。 如果異常類的名稱看不出原因,則需要在異常內(nèi)容中提供所需的信息矮固。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5.盡可能首先捕捉最具體的異常
大多數(shù)IDE都可以幫你完成失息。 當(dāng)你嘗試首先捕獲不太具體的異常時(shí),它們會(huì)報(bào)告代碼塊無法被訪問档址。
比如盹兢,如果你首先捕獲IllegalArgumentException ,則永遠(yuǎn)不會(huì)到達(dá)處理更具體的NumberFormatException的catch塊辰晕,因?yàn)樗荌llegalArgumentException的子類蛤迎。
始終首先捕獲最具體的異常類,并將不太具體的catch塊添加到列表的末尾含友。
你可以在以下代碼段中看到此類try-catch語(yǔ)句的示例替裆。 第一個(gè)catch塊處理所有NumberFormatException ,第二個(gè)catch塊處理非NumberFormatException的IllegalArgumentException 窘问。
public void catchMostSpecificExceptionFirst() {
try {
doSomething("A message");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
6.不要捕獲Throwable
Throwable是所有異常和錯(cuò)誤的超類辆童。 你可以在catch子句中捕獲它,但你永遠(yuǎn)不應(yīng)該這樣做惠赫!
如果在catch子句中捕獲了Throwable 把鉴,它不僅會(huì)捕獲所有異常; 它還會(huì)捕獲所有錯(cuò)誤。 JVM拋出錯(cuò)誤以指示應(yīng)用程序無法處理的嚴(yán)重問題。 典型的例子是OutOfMemoryError或StackOverflowError 庭砍。 兩者都是由應(yīng)用程序無法控制的情況引起的场晶,無法處理。
所以怠缸,最好不要捕獲Throwable诗轻,除非你完全確定你需要捕獲它。
public void doNotCatchThrowable() {
try {
// do something
} catch (Throwable t) {
// don't do this!
}
}
7.不要忽略捕捉到的異常
剛開始的時(shí)候揭北,開發(fā)人員可能非常確定這個(gè)異常永遠(yuǎn)不會(huì)發(fā)生扳炬,他甚至加上一個(gè)著名的“這將永遠(yuǎn)不會(huì)發(fā)生”的評(píng)論。
public void doNotIgnoreExceptions() {
try {
// do something
} catch (NumberFormatException e) {
// this will never happen
}
}
可是搔体,請(qǐng)永遠(yuǎn)不要忽視異常恨樟。 你不知道代碼將來會(huì)如何變化。
你至少應(yīng)該寫一條日志消息疚俱,告訴大家不可想象的事情剛剛發(fā)生劝术。
public void logAnException() {
try {
// do something
} catch (NumberFormatException e) {
log.error("This should never happen: " + e);
}
}
8.處理異常是,不要僅僅記錄然后就重新拋出它
這可能是最常見的錯(cuò)誤做法计螺, 你可以找到許多類似的代碼:
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
在發(fā)生異常時(shí)記錄異澈痪。可能會(huì)很直觀,然后重新拋出它以便調(diào)用者可以適當(dāng)?shù)靥幚硭?但這樣做會(huì)造成錯(cuò)誤被重復(fù)記錄在日志中登馒。
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)
其他消息也不會(huì)添加任何信息匙握。 如最佳實(shí)踐#4中所述,異常消息應(yīng)描述異常事件陈轿。 堆棧跟蹤告訴您拋出異常的類圈纺,方法和行。
如果需要添加其他信息麦射,則應(yīng)捕獲異常并將其包裝在自定義異常中蛾娶。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
因此,如果你捕捉了異常潜秋,請(qǐng)完整的處理它蛔琅,而不是僅僅記錄然后有拋出。 如果不需要處理峻呛,就在方法簽名中拋出異常讓調(diào)用者來處理罗售。
9.保留原始異常
有時(shí)候我們捕獲系統(tǒng)異常以后,會(huì)將其包裝成自定義異常钩述。
請(qǐng)確保將原始異常被保留寨躁。 Exception類提供了接受Throwable作為參數(shù)的特定構(gòu)造函數(shù)方法。 如果你不傳遞原始異常牙勘,將丟失原始異常的堆棧跟蹤和消息职恳。
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e);
}
}
總結(jié)
正如您所看到的,當(dāng)您拋出或捕獲異常時(shí),您應(yīng)該考慮許多不同的事情放钦。 其中大多數(shù)都旨在提高代碼的可讀性或API的可用性色徘。
異常是Java的錯(cuò)誤處理機(jī)制。 你應(yīng)該與同事一起討論如何使用異常最筒,以便每個(gè)人都能認(rèn)同并以相同的規(guī)則使用異常贺氓。