天天看點

【讀書筆記】《Effective Java》(8)--異常異常

沒想到今天完成了兩章筆記的總結,這部分内容感覺沒有泛型那部分難懂,不過要想熟練運用還是要多寫代碼才行,畢竟照以往我的代碼,異常處理都是很草率的。

下一部分是并發,這一章有些難度,還是先看看其他博文掃掃盲再看書。

異常

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

  • 異常處理相比于其他代碼沒有進行更多的優化,甚至會阻止編譯器對代碼的優化,是以不要将本應該在業務邏輯中處理的控制流放到try-catch中處理
  • 對于設計API來說,設計良好的API不應當強迫它的客戶代碼為了正常的控制流而使用異常。如果類具有“狀态相關”的方法,這個類應當配以“狀态測試”方法,以訓示是否可以調用這個狀态相關的方法;另外一個方法是如果類的執行個體處于不适當的狀态時,調用“狀态相關”的方法,傳回可識别的值,比如null。
  • 對于上面提到的“狀态測試”和傳回可識别的值兩種做法,權衡的原則是如果存在并發通路的情況,或者“狀态檢測”方法執行的工作會重複“狀态相關”方法的工作,從安全性和性能角度考慮,應當使用傳回可識别的值的做法,其他情況應當使用“狀态檢測”,因為“狀态檢測”提供了更好的可讀性,并且更易于查錯。

58. 對可恢複的情況使用受檢異常,對程式設計錯誤使用運作時異常

  • Throwable類下有三個子類:受檢的異常(checked exception)、運作時異常(run-time exception)以及錯誤(error)
  • 如何選擇抛出的異常類型:
    1. 如果期望調用者能夠使當地恢複,應當使用受檢的異常
    2. 用運作時異常表明程式設計錯誤
  • 一些規則:
    1. Error類按照慣例被JVM保留用于表示資源不足、限制失敗、或者其他是程式無法繼續執行的條件,最好不要自己擴充Error。所有未受檢的可抛出類都應當是RuntimeException的子類
    2. 自定義一個有别于受檢異常、運作時異常、錯誤的可抛出類也是可以的,從行為意義上它們等同于受檢異常,但永遠不要這麼做,這會給使用它們的人帶來困惑。

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

  • 使用受檢異常的情況:除以下兩種情況之外,使用受檢的異常被視為是不正确的,應當使用運作時異常
    1. 正确地調用API并不能阻止異常的發生
    2. 一旦出現異常,調用異常的程式員可以立刻采取有用的行為
  • 将受檢異常變為運作時異常的方法:将這個抛出異常的方法分成兩個 方法,其中一個方法傳回boolean,表明是否應該抛出異常。這種方法形式類似于第57條提到的“狀态檢測”方法,是以同樣,“狀态檢測”方法的問題(并發、性能)也适用于這種方法。

60. 優先使用标準的異常

  • 使用現有異常的好處:
    1. 是客戶代碼更加易于學習和使用
    2. 可讀性更好
    3. 異常類型越少,開銷越少
  • 一些常見的異常類型:
    異常類型 使用場合
    IllegalArgumentException 非null的參數值不正确
    IllegalStateExcepition 對于方法調用而言,對象狀态不适合
    NullPointerException 在禁止使用null的情況下使用null
    IndexOutOfBoundsException 下表參數值越界
    ConcurrentModificationException 在禁止并發修改的情況下,檢測到并發修改
    UnSupportedOperationException 對象不支援使用者請求的方法

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

  • 問題:如果方法調用的底層抛出了異常,這個異常和本方法可能沒有明顯的聯系,這會使調用方法的使用者感到奇怪。而且,這種情況同樣暴露了實作細節,如果以後更改實作方法,異常類型也會随之變動。
  • 本條建議:如果不能阻止或者處理來自底層的異常,更高層的實作應當捕獲底層的異常,同時抛出可以按照高層抽象進行解釋的異常,這種方法被成為異常轉義,但這最好不要濫用。更好的做法是調用底層方法時確定它可以被執行成功(比如傳參的有效性),或者高層不抛出底層的異常(這裡我了解是高層自己解決掉異常),将其記錄,而不将其展示給客戶代碼。
  • 對于高層包裝來自底層的異常并重抛這種異常轉義的方法,有一種叫做異常鍊的形式。通過構造新的異常,并将底層異常傳入構造器,防止底層異常丢失,對于沒有支援鍊的異常(自定義的,繼承自Throwable的),可以通過initCase方法設定原因。

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

  • 始終要單獨地聲明受檢的異常,并利用JavaDoc的

    @throw

    标記,準确記錄下抛出異常的條件,不要簡單地概括throw Exception,如果一個類中多個方法出于同樣的原因抛出了同一個異常,在該類的文檔注釋而不是方法的注釋中為這個異常建立文檔是可以的。
  • 對于運作時異常,應當為它們建立起良好的文檔,尤其對于接口,因為這份文檔構成了該接口的通用約定。雖然這一點(為運作時異常建立文檔)并非那麼容易遵守。

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

  • 對于未捕獲的異常,系統會調用異常的toString方法列印該異常的堆棧資訊,如果toString設計的不夠好,而這個異常又難以重制,擷取更有用的資訊會很難。
  • 本條建議:
    1. 為了捕獲失敗,異常的細節資訊應當包含所有“對該異常有貢獻”的參數和域的值。
    2. 應當區分異常的細節和“使用者層次的錯誤資訊”,對于調試程式的維護人員,更重要的是資訊的内容而不是可讀性
    3. 有些異常類(例如IndexOutOfBoundsException,但并不多)提供了使用細節資訊的構造方法,對于IndexOutOfBoundsException有

      public IndexOutOfBoundsException(int lowerBound,int upperBound,int index)

      這樣的構造器,來確定異常的細節資訊包含足夠多的能描述捕獲失敗的消息,應當推薦。

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

  • 失敗原子性:指失敗的方法調用應當是對象保持在被調用之前的狀态。
  • 獲得失敗原子性的方法:
    1. 檢查參數的有效性
    2. 調整計算過程的順序,是的任何可能會導緻失敗的計算部分在對象狀态被修改之前發生。
    3. 編寫恢複代碼,讓對象復原到失敗之前,但這中方法并不常用,主要用于持久性的資料結構
    4. 對對象的拷貝進行操作,例如,Collections.sort就是在内部将輸入清單轉到一個數組,還提高了性能
  • 不适合實作失敗原子性的情況:此時應當将其記入API文檔中,指明失敗時對象将在什麼狀态,是否可用
    1. 并發修改對象導緻狀态不一緻:這種情況下通常是不可恢複的
    2. 保持失敗原子性會顯著增加開銷或者複雜性的時候

65. 不要忽略異常

  • 即使對于不需要捕獲的異常,也要寫一句注釋,标明為什麼可以忽略這個異常
  • 特殊的情況:關閉FileInputStream時,可以忽略。因為沒有改變檔案狀态,是以我們不必進行任何恢複操作;而且我們已經從檔案中讀取所需資訊,是以不需要終止進行的操作。即使此種情景,記錄異常資訊也是明智的.隻要發生異常,你可以調查異常原因。(這句話我沒動,我搜尋了下,并沒有搜到相關的解釋,單單寫下來,以供記錄)
上一篇: OVN實踐