此文已由作者謝蕾授權網易云社區發布。
歡迎訪問網易云社區,了解更多網易技術產品運營經驗。
前言
我們對于“異常處理”這個詞并不陌生,眾多框架和庫在異常處理方面都提供了便利,但是對于何種處理才是最佳實踐,也是眾說紛紜。異常處理是否得當直接關系到軟件的健壯性,今天就談談我對異常處理這件事兒的拙見。首先,先說一下異常處理的通俗解釋:當危險或知道事情不對的時候做出的反饋。
目錄結構Java的異常分類
常見的異常處理的方法
推薦的實踐方式
1. Java的異常分類
先簡單用圖介紹一下Java的異常分類:
JAVA這種面向對象的語言,同樣把異常當作對象來處理,Throwable作為所有異常的超類,然后定義了許多異常類,將這些異常類分為兩大類:錯誤Error和異常Exception。其中,Exception異常類又分為RuntimeException和Checked Exception(也叫非運行時異常)。 Error是程序無法處理的錯誤,比如OutofMemoryError、TheadDeath等。Exception是程序本身可以處理的異常,應當盡可能去處理這些異常。運行時異常都是RuntimeException類及其子類異常,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理,如NullPointerException。通常,這些異常一般是由錯誤代碼邏輯引起的,我們應該從邏輯角度盡可能避免這類異常的發生。 非運行時異常從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException 、SQLException等以及用戶自定義的異常,一般情況下不自定義運行時異常。
2. 常見的異常處理方法
根據Java工程分層的思想,異常處理也是基于分層的思想。每層都和他的上下層之間都有一個契約,契約內容中就包含了異常處理。任何一層都可能遇到不測,如果遇到了未知的錯誤,不知道如何處理時,通常的做法就是愉快的向上層拋出一個異常,渴求有一層能正確處理掉這個可憐的錯誤。我們常用的Java異常處理機制通常依賴于try、catch、finally、throw、throws。?常見的異常處理方式如下:吞掉并拋出
打印log并拋出
嵌套處理并拋出
忽略異常
2.1 吞掉并拋出try{
...
}catch(Exception?e){throw?new?ApiException(ApiErrorCode.SqlErr,?"Failed?to?get?JobID.");
}
注:ApiException是程序自定義的異常,與SQLEXCEPTION、RUNTIMEEXCEPTION等都屬于異常的一種。 比較明顯,這種處理方式導致丟失了真實的錯誤信息,不利于上層做出正確的處理。非常不好的實踐。
2.2 打印log并拋出try{
boolean?ret?=?sendNotify(info);
if(ret){
i.remove();
}
}catch(ParseException?|?IOException?e){
logger.error("unexpected?exception?happened?while?send?notify?to?client!?remove?notify?message!?jobID:?"?+?info.getJobID(),?e);
throw?e;
}
這個做法想必大家經常看到,在許多工程中都用了這樣的做法。但是這樣的處理方式也在一定環境下也有問題,比如,當異常發生在調用層次較深的底層時,同樣的錯誤信息處理方式,會導致我們為了定位問題將看到無數的相同錯誤信息。所以,具體的異常處理方法還需要綜合上下文來處理。
2.3 嵌套處理并拋出try{
...
}catch(SQLException?e){throw?new?XxxException("Error?in?Xxx",?e);
}
這段代碼catch住了sql異常,然后將其封裝為另外一種異常拋出,期望遇到知心人能讀懂它的良苦用心。
2.4 忽略異常try{
...
}catch(SQLException?ex){
ex.printStacktrace();
}
經常看到這樣的低級錯誤嗎?是的,開發者無情的忽略了異常,并且只把異常信息輸出到控制臺,然后,程序仍然繼續運行,非常有可能導致更多的異常。
3.推薦的實踐方式
此節不敢稱為最佳實踐,因為一切都會隨著空間和時間的變化而變化。只能說下筆者見過的對異常處理的一些推薦做法。如有不適當的地方,請大家指正。如果不知道如何處理異常,最好在定義方法時throws該異常,可以聲明多個異常。
細化異常,盡可能的指定具體的異常,方便問題定位,盡量不要使用范圍太大的Exception。但是,有時一定要最大范圍捕獲的,比如一些線程不想讓它意外退出,那就必須通過最外層捕獲它了。
避免過大的try塊(對所有可能出現異常的代碼進行try塊的包裝),這樣不利于分析問題產生的原因。
一個try對應多個catch時,catch的異常定義可以由精確到一般。
不要無視捕獲的異常,小心因小失大。捕獲到后要么直接拋出,要么重新拋出新類型的異常。
不要在finally塊中return或throw等終止方法的語句,這樣會導致try或catch塊中的return或throw失效。
鼓勵程序適時的進行自定義異常類(非檢測異常的一種),因為異常的類名往往包含了很多有用信息,我們封裝后的異常類可以更加具體的根據代碼或業務處理區分異常類型,方便查找錯誤。
調試時可以使用printStackTrace()方法,但是發布后要避免使用該方法。
管理好自己和其他層之間的異常處理,契約精神,注意:不要把自己能處理的異常拋給別人。
網易云免費體驗館,0成本體驗20+款云產品!
更多網易技術、產品、運營經驗分享請點擊。