java異常有效實踐

java異常有效實踐

異常在咱們的平時開發過程當中是很是尋常而且常常會面對的,咱們有不少方式來處理和使用異常。充分發揮異常的優勢能夠提升程序的可讀性,可靠性和可維護性。可是若是使用不當,也會帶來不少負面影響。java


參考 effective java 第三版中對於異常的一些優秀實踐來作一下總結:程序員

No.1 只針對異常的狀況才使用異常

異常應該只應用於異常的狀況下,永遠不要在正常的控制流中使用異常。編程

例如代碼:api

int index = 0;
try{
    while(true){
        System.out.println(strList.get(index++));
    }
}catch (ArrayIndexOutOfBoundsException e){}
複製代碼

上圖代碼的功能是遍歷輸出strList集合中的所有元素,但其實咱們知道只須要一個foreach就能遍歷輸出 集合中的全部元素。也許可能考慮到了使用正常的foreach會使得每次遍歷的時候都要去檢查當前遍歷索引是否越界,覺得該種方式在性能方面會更優於正常的處理方式。實際上該種方式比正常的處理要慢(其中涉及到jvm的優化)。數組

總而言之,異常是爲了在異常狀況下使用而設計的,不要在正常的控制流中使用異常。jvm


No.2 對可恢復的狀況使用受檢異常,對編程錯誤使用運行時異常

若是指望調用者可以適當的恢復,就應該使用受檢異常。性能

用運行時異常來代表編程的錯誤。測試

你所實現的未受檢異常都應該是RunTimeException的子類。優化

要在受檢異常上提供方法,以便協助恢復。spa

不要定義任何既不是受檢異常也不是運行時異常的拋出類型。


No.3 避免沒必要要的使用受檢異常

受檢異常優勢:

不一樣於返回碼和未受檢異常,受檢異常強迫程序員處理異常的條件,從而增長程序的可靠性

受檢異常缺點:

1.若是方法拋出受檢異常,則調用該方法者就必須在一個try catch塊中對異常進行處理,或者在調用方法中聲明拋出異常並讓他們傳播出去。這給程序員帶來了很多的負擔。

2.拋出受檢異常的方法沒法直接在Stream中使用。

對於使用受檢異常的狀況應該同時知足兩個條件:

1.正確的使用該方法或者api的狀況下不能防止異常發生

2.一旦產生異常程序員能夠採起有效的措施來處理異常

若這兩點沒有同時成立,則更適合使用未受檢異常。

消除受檢異常的方法:

1.方法返回一個optional類型的對象,若遇到異常,則只是返回一個0長度的optional(該方法的缺點是因爲只返回一個optional,缺乏其餘信息,若發生異常追查緣由會比較困難)

2.把拋出異常的方法拆分紅兩個方法,第一個方法返回一個boolean值,代表是否應該拋出異常,另外一個方法則是該方法的處理邏輯。

3.若是程序員知道調用將會成功或者不介意因爲異常致使線程被終止。

合理的使用受檢異常能夠增長程序的可讀性和可靠性,若是過分使用受檢異常將會給調用者帶來很大的負擔。若是調用者沒法恢復異常則應該拋出未受檢異常。若是但願調用者對異常進行處理,首選應該是返回optional值,只有萬一失敗時,這些沒法提供足夠的信息來描述異常則考慮使用受檢異常。


No.4 優先使用標準的異常

異常重用,java平臺類庫提供了一組基本的非受檢異常,他們知足了大多數api的異常拋出需求。

最常被使用到的兩個異常類型:IllegalArgumentException和IllegalStateException。前者表明非法參數異常,後者表示非法狀態異常。能夠這麼說,全部錯誤的方法調用均可以歸結爲非法參數和非法狀態。另外還有其餘異常類也能夠表示爲非法參數和非法狀態異常 例如:NullPointException IndexOutOfBoundsException等等。

不要直接重用Exception,RunTimeException,Throwable 和 error。能夠把這些類當作是抽象類,你沒法可靠的測試這些異常,他們是一個方法可能拋出的異常的超類。

若是但願增長更多的失敗捕獲信息,能夠子類化標準異常。沒有正當的理由,不該該去編寫額外的異常累,而應該使用java提供的標準異常類。

對某一個異常發生的狀況可能同時知足多個標準異常規範的場合。好比 在長度爲10的數組中去取第11個元素。顯然這種狀況能夠理解參數數值太大(IllegalArgumentException),可是這種異常也能夠理解爲數組中的元素太少(IllegalStateException)。這裏咱們能夠規範若是沒有可用的參數值則使用後者異常類,不然使用前者。


No.5 拋出與抽象對應的異常

若是方法拋出的異常和他執行的任務沒有明顯的關聯,這種情形會令人不知所措。方法將他調用的底層方法異常原封不動的向外拋出,例如在一個獲取用戶信息的方法中調用了手機號解碼方法,而該解碼方法恰好發生異常,用戶信息方法將其捕捉以後直接拋出,這就會讓調用獲取用戶信息方法的調用者很困惑,由於他們並不知道獲取用戶信息和解碼異常之間的關係,從而致使問題並很差排查,究竟是客戶端傳參數不對仍是系統的異常。全部爲了不這個問題,更高層的異常應該捕獲底層的異常,同時拋出可按照高層抽象進行解釋的異常。這過程也叫異常轉譯。

有一種特殊的異常轉譯叫作異常鏈,即底層放入異常被傳到高層的異常,高層的異常提供訪問方法還得到底層的異常

try {
    URLEncoder.encode("ds","utf-8");
} catch (UnsupportedEncodingException e) {
    throw new IllegalArgumentException(e);
}
複製代碼

如上,高層異常的構造器將緣由傳到支持鏈的構造器,從而當異常發生時高層調用者能夠調用異常的相關方法來看到底層的異常,另外在打印異常堆棧信息的時候,這樣就能夠把底層的異常信息給集成到高層異常中。

異常轉譯相對於直接將底層異常進行拋出會好不少,但咱們不該該濫用。對於底層方法的異常咱們首先要作的就是應該避免會發生這種異常,例如在調用以前進行參數校驗從而防止異常發生。固然底層方法發生異常時,咱們其次想到的應該是在高層方法中悄悄的處理異常,從而將高層方法的調用者和異常進行隔離,使用log對異常進行記錄,這樣有助於排查問題,又能夠將客戶端代碼和最終用戶與異常隔離開來。

若是不能阻止而且處理底層異常的發生,咱們應該使用異常轉譯,只有底層拋出的異常剛好能表述高層執行任務的狀況下,能夠將底層異常直接進行拋出。


No.6 每一個方法拋出的全部異常都要創建文檔

始終要單獨的申明每個受檢異常,而且利用javadoc的@throws標籤,準確的記錄下每一個異常拋出的條件。而且須要拋出具體的異常類而非異常的基類exception或者throwable。

使用javadoc的@throws標籤記錄一個方法可能會拋出的未受檢異常,但不要使用throws關鍵字將未受檢異常包含在方法申明中。

若是一個類中的許多方法在一樣的狀況下都會拋出一致的異常,那麼在該類的文檔註釋中應該對這個異常創建文檔,而不是爲每個方法創建文檔。


No.7 在細節消息中包含失敗-捕獲信息

爲了捕獲失敗,異常的細節信息應該包含對異常有貢獻的全部參數和域的值。不過千萬不要在細節中包含密碼,密鑰等敏感信息。


No.8 努力保持失敗的原子性

通常而言,失敗的方法調用應該使對象保持在被調用以前的狀態,具備這種屬性的方法被稱爲具備失敗的原子性。

保持失敗的原子性:

1.設計一個不可變的對象。

2.在執行操做以前進行必要的參數有效性校驗,這使得對象狀態被修改以前先拋出適當的異常(調整計算處理的順序,使得任何可能會失敗的計算部分都在對象被修改前發生)。

3.在對象的一份臨時拷貝上進行操做,當操做完成後在用臨時的拷貝來替換原有的對象。

4.寫一段恢復的代碼,讓他來攔截過程當中發生的失敗,讓對象回到被調用前的狀態。

錯誤一般是不可恢復的,不要在方法拋出assertionError時,不須要努力的去保持失敗的原子性。


No.9 不要忽略異常

不要忽略異常,忽略異常很簡單,使用一個try 並利用空的catch塊就能忽略異常,可是空的catch達不到應有的目的。咱們能夠把異常認爲是火災而trycatch就像是火警器。當異常發生時咱們沒有作任何的處理而是將他忽略。這將致使沒人會在乎到這個異常。當真正有一條異常被注意到的時候也許這個異常影響的範圍已經很是大了。

也有一些異常咱們是能夠忽略的。相似文件讀取,fileStream關閉操做,當咱們對文件沒有作任何處理,而且已經讀取到文件中的信息,此時咱們能夠將異常進行忽略。可是忽略的時候咱們應該做出必要的註釋說明。而且將catch中的類變量名設爲ignore。

總之,經過忽略異常來解決異常是一件極具風險的事情,咱們須要認真對待異常,而且十分清楚問題的緣由以及產生的影響。

相關文章
相關標籤/搜索