java 异常 最佳实践_关于JAVA异常处理的20个最佳实践
在我們深入了解異常處理最佳實(shí)踐的深層概念之前,讓我們從一個最重要的概念開始,那就是理解在JAVA中有三種一般類型的可拋類:?檢查性異常(checked exceptions)、非檢查性異常(unchecked Exceptions) 和?錯誤(errors)。
異常類型
檢查性異常(checked exceptions)?是必須在在方法的throws子句中聲明的異常。它們擴(kuò)展了異常,旨在成為一種“在你面前”的異常類型。JAVA希望你能夠處理它們,因?yàn)樗鼈円阅撤N方式依賴于程序之外的外部因素。檢查的異常表示在正常系統(tǒng)操作期間可能發(fā)生的預(yù)期問題。 當(dāng)你嘗試通過網(wǎng)絡(luò)或文件系統(tǒng)使用外部系統(tǒng)時,通常會發(fā)生這些異常。 大多數(shù)情況下,對檢查性異常的正確響應(yīng)應(yīng)該是稍后重試,或者提示用戶修改其輸入。
非檢查性異常(unchecked Exceptions)?是不需要在throws子句中聲明的異常。 由于程序錯誤,JVM并不會強(qiáng)制你處理它們,因?yàn)樗鼈兇蠖鄶?shù)是在運(yùn)行時生成的。 它們擴(kuò)展了RuntimeException。 最常見的例子是NullPointerException?[相當(dāng)可怕..是不是?]。 未經(jīng)檢查的異常可能不應(yīng)該重試,正確的操作通常應(yīng)該是什么都不做,并讓它從你的方法和執(zhí)行堆棧中出來。 在高層次的執(zhí)行中,應(yīng)該記錄這種類型的異常。
錯誤(errors)?是嚴(yán)重的運(yùn)行時環(huán)境問題,幾乎肯定無法恢復(fù)。 例如OutOfMemoryError,LinkageError和StackOverflowError, 它們通常會讓程序崩潰或程序的一部分。 只有良好的日志練習(xí)才能幫助你確定錯誤的確切原因。
用戶自定義異常
任何時候,當(dāng)用戶覺得他出于某種原因想要使用自己的特定于應(yīng)用程序的異常時,他可以創(chuàng)建一個新的類來適當(dāng)?shù)臄U(kuò)展超類(主要是它的Exception.java)并開始在適當(dāng)?shù)牡胤绞褂盟?這些用戶定義的異常可以以兩種方式使用:
1) 當(dāng)應(yīng)用程序出現(xiàn)問題時,直接拋出自定義異常
throw new DaoObjectNotFoundException("Couldn't find dao with id " + id);
2) 或者將自定義異常中的原始異常包裝并拋出
catch(NoSuchMethodException e) {throw new DaoObjectNotFoundException("Couldn't find dao with id " +id, e);
}
包裝異常可以通過添加自己的消息/上下文信息來為用戶提供額外信息,同時仍保留原始異常的堆棧跟蹤和消息。 它還允許你隱藏代碼的實(shí)現(xiàn)細(xì)節(jié),這是封裝異常的最重要原因。
現(xiàn)在讓我們開始探索遵循行業(yè)聰明的異常處理的最佳實(shí)踐。
你必須考慮并遵循的最佳做法
1) 永遠(yuǎn)不要吞下catch塊中的異常
catch(NoSuchMethodException e) {return null;
}
2) 在你的方法里拋出定義具體的檢查性異常
public void foo() throws Exception { //錯誤方式
}
一定要避免出現(xiàn)上面的代碼示例。 它簡單地破壞了檢查性異常的整個目的。 聲明你的方法可能拋出的具體檢查性異常。 如果只有太多這樣的檢查性異常,你應(yīng)該把它們包裝在你自己的異常中,并在異常消息中添加信息。 如果可能的話,你也可以考慮代碼重構(gòu)。
public void foo() throws SpecificException1, SpecificException2 { //正確方式
}
3) 捕獲具體的子類而不是捕獲Exception類
try{
someMethod();
}catch (Exception e) { //錯誤方式
LOGGER.error("method has failed", e);
}
捕獲異常的問題是,如果稍后調(diào)用的方法為其方法聲明添加了新的檢查性異常,則開發(fā)人員的意圖是應(yīng)該處理具體的新異常。 如果你的代碼只是捕獲異常(或Throwable),你永遠(yuǎn)不會知道這個變化,以及你的代碼現(xiàn)在是錯誤的,并且可能會在運(yùn)行時的任何時候中斷。
4) 永遠(yuǎn)不要捕獲Throwable類
這是一個更嚴(yán)重的麻煩。 因?yàn)閖ava錯誤也是Throwable的子類。 錯誤是JVM本身無法處理的不可逆轉(zhuǎn)的條件。 對于某些JVM的實(shí)現(xiàn),JVM可能實(shí)際上甚至不會在錯誤上調(diào)用catch子句。
5) 始終正確包裝自定義異常中的異常,以便堆棧跟蹤不會丟失
catch(NoSuchMethodException e) {throw new MyServiceException("Some information: " + e.getMessage()); //錯誤方式
}
這破壞了原始異常的堆棧跟蹤,并且始終是錯誤的。 正確的做法是:
catch(NoSuchMethodException e) {throw new MyServiceException("Some information: " , e); //正確方式
}
6) 要么記錄異常要么拋出異常,但不要一起執(zhí)行
catch(NoSuchMethodException e) {//錯誤方式
LOGGER.error("Some information", e);throwe;
}
正如在上面的示例代碼中,記錄和拋出異常會在日志文件中產(chǎn)生多條日志消息,代碼中存在單個問題,并且讓嘗試挖掘日志的工程師生活得很糟糕。
7) finally塊中永遠(yuǎn)不要拋出任何異常
try{
someMethod();//Throws exceptionOne
} finally{
cleanUp();//如果finally還拋出異常,那么exceptionOne將永遠(yuǎn)丟失
}
只要cleanUp()永遠(yuǎn)不會拋出任何異常,上面的代碼沒有問題。 但是如果someMethod()拋出一個異常,并且在finally塊中,cleanUp()也拋出另一個異常,那么程序只會把第二個異常拋出來,原來的第一個異常(正確的原因)將永遠(yuǎn)丟失。 如果你在finally塊中調(diào)用的代碼可能會引發(fā)異常,請確保你要么處理它,要么將其記錄下來。 永遠(yuǎn)不要讓它從finally塊中拋出來。
8) 始終只捕獲實(shí)際可處理的異常
catch(NoSuchMethodException e) {throw e; //避免這種情況,因?yàn)樗鼪]有任何幫助
}
這是最重要的概念。 不要為了捕捉異常而捕捉,只有在想要處理異常時才捕捉異常,或者希望在該異常中提供其他上下文信息。 如果你不能在catch塊中處理它,那么最好的建議就是不要只為了重新拋出它而捕獲它。
9) 不要使用printStackTrace()語句或類似的方法
完成代碼后,切勿忽略printStackTrace()。 你的同事可能會最終得到這些堆棧,并且對于如何處理它完全沒有任何知識,因?yàn)樗粫郊尤魏紊舷挛男畔ⅰ?/p>
10) 如果你不打算處理異常,請使用finally塊而不是catch塊
try{
someMethod();//Method 2
} finally{
cleanUp();//do cleanup here
}
這也是一個很好的做法。 如果在你的方法中你正在訪問Method 2,而Method 2拋出一些你不想在method 1中處理的異常,但是仍然希望在發(fā)生異常時進(jìn)行一些清理,然后在finally塊中進(jìn)行清理。 不要使用catch塊。
11) 記住“早throw晚catch”原則
這可能是關(guān)于異常處理最著名的原則。 它基本上說,你應(yīng)該盡快拋出(throw)異常,并盡可能晚地捕獲(catch)它。 你應(yīng)該等到你有足夠的信息來妥善處理它。
這個原則隱含地說,你將更有可能把它放在低級方法中,在那里你將檢查單個值是否為空或不適合。 而且你會讓異常堆棧跟蹤上升好幾個級別,直到達(dá)到足夠的抽象級別才能處理問題。
12) 清理總是在處理異常之后
如果你正在使用數(shù)據(jù)庫連接或網(wǎng)絡(luò)連接等資源,請確保清除它們。 如果你正在調(diào)用的API僅使用非檢查性異常,則仍應(yīng)使用try-finally塊來清理資源。 在try模塊里面訪問資源,在finally里面最后關(guān)閉資源。 即使在訪問資源時發(fā)生任何異常,資源也會優(yōu)雅地關(guān)閉。
13) 只從方法中拋出相關(guān)異常
相關(guān)性對于保持應(yīng)用程序清潔非常重要。 一種嘗試讀取文件的方法; 如果拋出NullPointerException,那么它不會給用戶任何相關(guān)的信息。 相反,如果這種異常被包裹在自定義異常中,則會更好。 NoSuchFileFoundException則對該方法的用戶更有用。
14) 切勿在程序中使用流程控制異常
我們已經(jīng)閱讀過很多次,但有時我們還是會在項(xiàng)目中看到開發(fā)人員嘗試為應(yīng)用程序邏輯而使用異常的代碼。 永遠(yuǎn)不要這樣做。 它使代碼很難閱讀,理解和丑陋。
15) 驗(yàn)證用戶輸入以在請求處理的早期捕獲不利條件
始終要在非常早的階段驗(yàn)證用戶輸入,甚至在達(dá)到實(shí)際controller之前。 它將幫助你把核心應(yīng)用程序邏輯中的異常處理代碼量降到最低。 如果用戶輸入出現(xiàn)錯誤,它還可以幫助你使與應(yīng)用程序保持一致。
例如:如果在用戶注冊應(yīng)用程序中,你遵循以下邏輯:
1)驗(yàn)證用戶
2)插入用戶
3)驗(yàn)證地址
4)插入地址
5)如果出問題回滾一切
這是非常不正確的做法。 它會使數(shù)據(jù)庫在各種情況下處于不一致的狀態(tài)。 首先驗(yàn)證所有內(nèi)容,然后將用戶數(shù)據(jù)置于dao層并進(jìn)行數(shù)據(jù)庫更新。 正確的做法是:
1)驗(yàn)證用戶
2)驗(yàn)證地址
3)插入用戶
4)插入地址
5)如果問題回滾一切
16) 一個異常只能包含在一個日志中
LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");
不要這樣做。
對多個LOGGER.debug()調(diào)用使用多行日志消息可能在你的測試用例中看起來不錯,但是當(dāng)它在具有400個并行運(yùn)行的線程的應(yīng)用程序服務(wù)器的日志文件中顯示時,所有轉(zhuǎn)儲信息都是相同的日志文件,你的兩個日志消息最終可能會在日志文件中間隔1000行,即使它們出現(xiàn)在代碼的后續(xù)行中。
像這樣做:
LOGGER.debug("Using cache sector A, using retry sector B");
17) 將所有相關(guān)信息盡可能地傳遞給異常
有用且信息豐富的異常消息和堆棧跟蹤也非常重要。 如果你的日志不能確定任何事情(有效內(nèi)容不全或很難確定問題原因),
那要日志有什么用? 這類的日志只是你代碼中的裝飾品。
18) 終止掉被中斷線程
while (true) {try{
Thread.sleep(100000);
}catch (InterruptedException e) {} //別這樣做
doSomethingCool();
}
InterruptedException是你的代碼的一個提示,它應(yīng)該停止它正在做的事情。 線程中斷的一些常見用例是active事務(wù)超時或線程池關(guān)閉。 你的代碼應(yīng)該盡最大努力完成它正在做的事情,并且完成當(dāng)前的執(zhí)行線程,而不是忽略InterruptedException。 所以要糾正上面的例子:
while (true) {try{
Thread.sleep(100000);
}catch(InterruptedException e) {break;
}
}
doSomethingCool();
19) 使用模板方法重復(fù)try-catch
在你的代碼中有100個類似的catch塊是沒有用的。 它增加代碼的重復(fù)性而且沒有任何的幫助。 對這種情況要使用模板方法。
例如,下面的代碼嘗試關(guān)閉數(shù)據(jù)庫連接。
classDBUtil{public static voidcloseConnection(Connection conn){try{
conn.close();
}catch(Exception ex){//Log Exception - Cannot close connection
}
}
}
這種類型的方法將在你的應(yīng)用程序的成千上萬個地方使用。 不要把這塊代碼放的到處都是,而是定義頂層的方法,并在下層的任何地方使用它:
public voiddataAccessCode() {
Connection conn= null;try{
conn=getConnection();
....
}finally{
DBUtil.closeConnection(conn);
}
}
20) 在JavaDoc中記錄應(yīng)用程序中的所有異常
把注釋(javadoc)運(yùn)行時可能拋出的所有異常作為一種習(xí)慣。
也要盡可能包括可行的方案,用戶應(yīng)該關(guān)注這些異常發(fā)生的情況。
這就是我現(xiàn)在所想的。 如果你發(fā)現(xiàn)任何遺漏或你與我的觀點(diǎn)不一致,請發(fā)表評論。 我會很樂意討論。
總結(jié)
以上是生活随笔為你收集整理的java 异常 最佳实践_关于JAVA异常处理的20个最佳实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java mapreduce 实例_Ma
- 下一篇: c 和java通讯大小端问题处理_记录一