Effective Java: 異常

57.只針對異常的情況才使用異常

簡介

錯誤示例:

try{
    int i = 0;
    while(true){
        rang[i++].climb();
    }
}catch(ArrayIndexOutOfBoundsException e){}
  • 異常機制的設計初衷是用於不正常的情形,所以很少會有 JVM實現視圖對他們進行優化
  • 把代碼放在 try-catch塊中反而阻止了現代 JVM本來可能要執行的某些優化.
  • 對數組進行遍歷 的標準模式 並不會導致 冗餘的檢查.

而正確的寫法,應該是:

for(Mountain m: range){
    m.climb();
}

錯誤的觀點:

認爲 VM對每次數組的訪問都要檢查越界情況,導致性能差.
使用 異常模式 可以提高性能

事實

在現代 JVM的實現中,基於異常的模式比標準模式 要慢得多,降低了性能.
基於異常模式會模糊代碼意圖,如果出現了bug,它還會掩蓋了 bug.

小結

  1. 異常應該只用於異常情況下,不應該用於正常的控制流.
  2. 設計良好的API不應該強迫它的客戶端爲了正常的控制流而是用異常.

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

簡介

Java 提供了三種可拋出結構: 受檢異常,運行時異常和錯誤.

使用規範

  • 如果期望調用者能夠適當的恢復,就應該使用受檢異常.

通過拋出受檢異常,強迫調用者處理異常

  • 運行時和錯誤都是不應該被捕獲的可拋出結構

如果程序拋出這個,往往都是不可恢復的情形

  • 運行時異常表明 編程錯誤
  • 錯誤往往是被JVM保留用於標示資源不足,約定失敗或者其他使程序無法繼續執行的條件

小結

對於 可恢復 的情況,使用受檢異常
對於程序錯誤,使用運行時異常.
受檢異常,往往需要指明一個可恢復的條件.

59.避免不必要的使用受檢的異常

簡介

上一個條目說 使用受檢的異常,調用者就需要在 catch中處理這個異常.
也就是說過分的使用 受檢異常 會使API 使用起來非常不方便,因爲需要在 一個或多個catch塊中處理這個異常. 或者 thows這些異常

小結

  1. 如果即便合理的調用了API也會遇到異常情形,並且捕獲異常之後能夠進行一些有意義的操作,才應該使用checked exception,其他情況下都應該使用RuntimeException
  2. 如果一個方法會拋出checked exception,都可以將其拆分爲兩個方法,一個用於判斷是否會拋出異常,另一部分用於處理正常情況,如果不符合約定,就拋出RuntimeException

60.優先使用標準的異常

Java 平臺類庫未受檢異常

代碼重用是值得提倡的,異常也不例外

重用現有的異常有多方面的好處:

  1. 使你的API更加易於學習和使用
  2. 對於用到這些API的程序而言,可讀性更好
  3. 異常類越少,意味着內存 印記 就越小,裝載這些異常時間開銷也越少

常用的有IllegalArgumentException,IllegalStateException

小結

選擇重用哪個異常,並不總是那麼精確.沒有嚴格的規則/.

61.拋出與抽象相對應的異常

簡介

  • 異常轉譯
    更高層的實現應該捕獲 低層的異常,同時拋出可以按照高層抽象進行解釋的異常. 就是 異常轉譯
// Exception Translation
try {
  // Use lower-level abstraction to do our bidding
  ...
} catch(LowerLevelException e) {
  throw new HigherLevelException(...);
}
  • 異常鏈
    如果低層的異常對於調試導致高層異常的問題非常有幫助,就可以使用異常鏈
// Exception Chaining
try {
  // Use lower-level abstraction to do our bidding
  ...
} catch(LowerLevelException e) {
  throw new HigherLevelException(e);
}

大多數標準的異常 都支持 異常連的構造器
如果不支持,可以使用 Throwable#initCause來設置
異常連也不能亂用,最好的做法是, 在調用低層方法之前確保他們會成功執行,從而避免他們拋出異常

小結

  1. 如果不能阻止或者處理來自更低層的異常,一般的做法是使用異常轉譯.
  2. 如果碰巧 低層方法可以保證它拋出的所有異常高層也合適,就可以將異常低層傳播到高層.

62.每個方法拋出的異常都要有文檔

簡介

  1. 不要通過聲明拋出多個異常的父類來實現拋出多種異常的效果。
  2. 要利用 javadoc@throws標記準確的記錄下拋出受檢異常的條件
  3. 不要使用@throws將未受檢的異常也包含在方法的聲明中
  4. 如果一個類的很多方法都拋出同一個異常,那麼可以將文檔放到class doc中,而不是method doc

63.在細節消息中包含能捕獲失敗的信息

簡介

  • 爲了捕獲失敗,異常的細節信息,應該包含所有”對該異常有貢獻”的參數和域的值.
  • 異常類型的toString方法應該儘可能多地返回有關失敗原因的信息.
  • 爲異常的”失敗捕獲”信息提供一些訪問方法是合適的.提供這樣的方法對於受檢異常,比未受檢異常更爲重要.

64.努力使失敗保持原子性

簡介

當對象拋出異常後,我們期望這個對象仍然保持在一種定義良好的可用狀態.

  1. 一般而言,失敗的方法調用,應該使對象保持在被調用之前的狀態
    具有這種屬性的方法被稱爲具有失敗原子性

創建具有失敗原子性的方法

  1. 最 簡單的方法 莫過於 設計一個不可變得對象,如果對象是不可變的,那顯然是具有原子性的
  2. 在可變對象上,最常見的方法是: 在執行操作之前,檢查參數的有效性,在對象的狀態被改變之前拋出適當的異常,如:
public Object pop(){
    if(size ==0){
        throw new EmptyStackException();
        //...
    }
}

如果不在 開始進行檢查,在執行過程中也會 拋出異常,然而會導致 size 域 保持在不一致的狀態,導致後續調用都會失敗

  1. 編寫一段 恢復代碼,由它來攔截操作過程中發生的失敗,以及使對象回滾到操作之前的狀態.
  2. 在對象的一份臨時拷貝上執行操作,當操作完成之後再用臨時拷貝中的內容替換對象中的內容

65.不要忽略異常

簡介

這條看似是顯而易見的.但是卻常常被違反,因此不可以忽視它
設計者聲明一個方法拋出異常,等於正在試圖說明什麼.

// Empty catch block ignores exception
try{}catch(Exception e){}
  1. 空的 catch塊,會使異常達不到應有的目的.
  2. 至少,catch塊也應該包含一條說明,說明爲什麼可以忽略這個異常.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章