整理 Effective Java 書中 Item 49: Check parameters for validity心得筆記

主旨

幾乎所有方法和建構子都會對輸入參數有一定的限制,例如 index 不可為負數、物件參照不可為 null 等。如果這些條件沒被滿足,我們應該要在一開始就檢查並丟出明確的例外,否則後果可能是錯誤難以追蹤,甚至導致物件狀態錯亂。

點出問題

如果不檢查參數是否合法,可能會發生下列問題:

  1. 方法執行到一半才炸掉,丟出難懂的例外。
  2. 方法「正常結束」但回傳錯誤結果。
  3. 更慘的是,物件進入錯誤狀態,後面程式碼才因為不相干的操作掛掉,讓人找不到問題源頭。

範例:模運算

/**
 * 回傳 this mod m 的結果(保證是非負數)
 * @param m 必須為正數
 * @return this mod m
 * @throws ArithmeticException 若 m 小於等於 0
 */
public BigInteger mod(BigInteger m) {
    if (m.signum() <= 0)
        throw new ArithmeticException("Modulus <= 0: " + m);
    // ...進行模運算
}

這段 code 的重點在於:

  • 清楚說明參數限制:m 必須為正。
  • 明確定義違反限制時要丟什麼例外(ArithmeticException)。
  • 一開始就檢查,避免之後出錯。

劃重點

✅ 公開方法應該怎麼做?

  • 用 Javadoc 的 @throws 註明例外。
  • 通常使用 IllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException 等。
  • Objects.requireNonNull() 檢查 null 值,比手動 if 判斷更簡潔。
this.strategy = Objects.requireNonNull(strategy, "strategy");

🛠 Java 9 提供的 index 檢查工具

Objects.checkFromToIndex(start, end, size);
Objects.checkIndex(index, size);
Objects.checkFromIndexSize(start, length, size);

缺點是不能自訂錯誤訊息,但用在 List/Array 索引的場景很方便。

🔒 私有或套件內部方法可以使用 assert

若你能保證呼叫方法的地方永遠不會給錯參數,那可以這樣寫:

private static void sort(long[] a, int offset, int length) {
    assert a != null;
    assert offset >= 0 && offset <= a.length;
    assert length >= 0 && length <= a.length - offset;
    // 排序邏輯
}

這些 assert 只會在開啟 -ea(enable assertions)時檢查,適合開發或測試用。

建構子一定要檢查

建構子如果沒有驗證參數,就有可能建立出狀態不合法的物件,違反類別不變式(class invariant),絕對要避免。

不要過度檢查

不是所有的檢查都值得做。若某些條件會在執行時自然導致合理例外,就不需要額外檢查。

舉例:

Collections.sort(list); // 若元素不可比較,會自然丟出 ClassCastException

如果你想強化例外訊息,也可以用「例外轉換技巧」(exception translation)來做:

try {
    // 嘗試某個可能因輸入錯誤而失敗的操作
} catch (SomeException e) {
    throw new IllegalArgumentException("參數不合法", e);
}

真實世界案例:

有一次曾經寫了一個方法,參數是 List<String>,但忘了檢查是否為 null。 結果 QA 在測試時發現某頁面會隨機爆 NullPointerException,log 卻完全看不出是哪裡錯。 最後花了快兩個小時才發現在某些條件下會傳 null,害整個功能崩掉。 從那之後,我就養成一定先檢查參數的習慣了。寧願一開始失敗,也不要後面出包。

小結

  • 記得針對所有方法/建構子的參數做合法性檢查。
  • 有條件就寫清楚、用 @throws 文件化,並在程式碼中顯式檢查。
  • Objects.requireNonNull() 是你 null 檢查的好朋友。
  • 私有方法用 assert,但不要在公開 API 裡依賴它。
  • 建構子尤其重要,因為物件一旦建立就是你的責任。
  • 失敗要早、要明確,否則會難以追蹤。

檢查參數雖然麻煩一點,但能省下很多除錯時間,是每個專業開發者都該養成的習慣。