【08】Effective Java - 異常

一、只針對異常的狀況才使用異常

(1)基於異常的模式比標準模式要慢不少
try{
   int i = 0;
   while(true)
     range[i++].climb();
}catch(ArrayIndexOutOfBoundsException e){
}

  企圖經過異常來終止循環,是對異常的誤用,異常是針對不正常狀況才使用的。上面的基於異常模式運行上,比正常的模式在性能上慢不少,2倍或以上。java


(2)不要將異經常使用於正常控制流

對於API的設計也是如此,不要強迫它的客戶端爲了正常的控制流而使用異常程序員

通常的處理方法有兩種:數組

A、使用狀態監測

    若是對象的狀態是線程安全的話,推薦使用,例如安全

for(Iterator<Foo> i = collection.iterator();i.hasNext();){
    Foo foo = i.next();
}

  這裏的hasNext就是狀態監測併發

  若是使用異常的話,客戶端就得這樣性能

try{
   Iterator<Foo> i = collection.iterator();
   while(true){
      Foo foo = i.next();
   }
}catch(NoSuchElementException e){
}

 

B、返回可識別的值

    對於iterator這種狀況,返回null是可行的。從性能角度看,返回可識別的值這種方法較好。學習


二、對可恢復的狀況使用受檢異常,程序錯誤使用運行時異常

(1)受檢異常(checked exception)

     若是指望調用者可以適當的恢復,對於這種狀況,應該使用受檢異常。this

     API設計者讓API用戶面對受檢異常,以強制用戶從這個異常條件中恢復過來,用戶能夠忽視這樣的強制要求,只需捕獲異常並忽略便可,但這每每不是一個好方法。spa


(2)運行時異常(runtime exception)

    一般用來代表程序錯誤,好比precondition violation,前提違例,即API的客戶沒有遵循API規範創建的約定線程


三、避免沒必要要的使用受檢的異常

    受檢異常本質上沒有給程序員提供任何好處,反而須要付出努力,使得程序變得複雜。

    把受檢異常變爲非受檢異常的一個方法,就是把拋出異常的方法拆分爲兩個,第一個方法返回boolean,代表是否應該拋出異常。例如iterator的hasNext,這種重構並不老是恰當的,可是若是用的合理,API使用起來就更加舒服。固然這種等同於狀態監測的方法,若是線程不安全的話,不推薦這種重構。


四、優先使用標準的異常

(1)重用現有異常的好處

 A、使得你的API更加易於學習

 B、可讀性更好一些

 C、異常類越少,則內存印跡越小,裝載這些類的開銷也就越少


(2)常見的通用異常

A、IllegalArgumentException

     傳遞參數不合適的時候

B、IllegalStateException

    由於接受對象的狀態而使得調用非法,好比在未初始化完畢以前使用對象

C、ConcurrentModificationException

    併發修改

D、UnsupportedOperationException

    好比對只支持追加操做的list進行刪除操做


五、拋出與抽象相對應的異常

(1)異常轉義

    若是方法拋出的異常與它所執行的任務沒有明顯的聯繫,這種情形將會使得人們不知所措,特別是當方法傳遞由低層抽象拋出的異常時,這除了讓人困惑以外,也讓實現細節污染了更高層的API。

    爲了不這個問題,須要進行異常轉義(exception transaction),即更高層的實現應該捕獲低層的異常,同時拋出能夠按照高層抽象進行解釋的異常。好比List<E>中的get方法,對低層AbstractSequentialList類拋出的異常進行轉義。

public E get(int index){
   ListIterator<E> i = listIterator(index);
   try{
      return i.next();
   }catch(NoSuchElementException e){
      throw new IndexOutOfBoundsException("Index:"+index);
   }
}


(2)異常鏈(exception chaining)

      異常鏈是異常轉義的特殊形式,若是低層的異常對於調試致使高層異常的問題很是有幫助的話,使用異常鏈就很合適。大多數標準的異常都有支持鏈的構造器(Throwable cause),對於沒有支持鏈的異常,能夠利用Throwable的initCause方法設置緣由。

     儘管異常轉譯與不加選擇地從低層傳遞異常的作法有所改進,可是它也不能被濫用。處理來自低層的異常的最好的作法就是,在調用低層方法以前,先把來自高層的參數校驗成功,從而儘量避免在低層拋出異常。


六、每一個方法拋出的異常都要有文檔

(1)利用@throws標記拋出的受檢異常

       同時記錄拋出該異常的條件

(2)不要泛泛地拋出Throwable,Exception

       這樣沒有給調用者任何準確的指導信息


七、在細節消息中包含能捕獲失敗的信息

(1)異常的字符串表示法

     即異常的toString方法,通常包含異常的類名以及異常細節

(2)異常細節應該包含全部「對該異常有貢獻」的參數和域的值

     好比IndexOutOfBoundsException的異常細節應該包含下界、上界以及沒有落在界內的下標值。

     能夠定義一個構造器,拋出異常時,傳入這些必要的參數,而後自動產生細節信息。

public IndexOutOfBoundsException(int lowerBound,int upperBound,int index){
    super("lower bound:"+lowerBound+
    ",upper bound:"+upperBound+
    ",index:"+index);
    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index = index;
}


(3)異常細節不該該與用戶層次的錯誤信息混爲一談

    異常細節通常是讓程序員去分析失敗緣由的


八、努力使失敗保持原子性

(1)失敗原子性

   即失敗的方法調用應該使對象保持在被調用以前的狀態。


(2)實現失敗原子性的方法

A、設計一個不可變對象

B、對於可變對象,在執行操做以前檢查參數有效性

    擴展:儘量調整計算過程的順序,使得可能失敗的部分發生在對象被修改以前

C、編寫一段恢復代碼(recovery code)

     不是很經常使用呢,能夠經過攔截處理

D、拷貝對象進行操做,成功以後用其替代對象的內容

     好比Collections.sort在執行排序以前,先把輸入列表轉到一個數組中,以便下降在排序的內循環中訪問元素所須要的開銷,另一個考慮就是當排序失敗的時候,也能保證輸入列表保持原樣。

總之,做爲方法規範的一部分,產生的任何異常都應該讓對象保持在該方法調用以前的狀態。


九、不要忽略異常

(1)空的catch塊會使異常達不到應有的目的

(2)不重複地記錄異常,能夠在必要的時候調查異常緣由

相關文章
相關標籤/搜索