Effective Java 第三版——49. 檢查參數有效性

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必不少人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到如今已經將近8年的時間,但隨着Java 6,7,8,甚至9的發佈,Java語言發生了深入的變化。
在這裏第一時間翻譯成中文版。供你們學習分享之用。
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。可是Java 9 只是一個過渡版本,因此建議安裝JDK 10。html

Effective Java, Third Edition

49.檢查參數有效性

本章(第8章)討論了方法設計的幾個方面:如何處理參數和返回值,如何設計方法簽名以及如何記載方法文檔。 本章中的大部份內容適用於構造方法和其餘普通方法。 與第4章同樣,本章重點關注可用性,健壯性和靈活性上。java

大多數方法和構造方法對能夠將哪些值傳遞到其對應參數中有一些限制。 例如,索引值必須是非負數,對象引用必須爲非null。 你應該清楚地在文檔中記載全部這些限制,並在方法主體的開頭用檢查來強制執行。 應該嘗試在錯誤發生後儘快檢測到錯誤,這是通常原則的特殊狀況。 若是不這樣作,則不太可能檢測到錯誤,而且一旦檢測到錯誤就更難肯定錯誤的來源。git

若是將無效參數值傳遞給方法,而且該方法在執行以前檢查其參數,則它拋出適當的異常而後快速且清楚地以失敗結束。 若是該方法沒法檢查其參數,可能會發生一些事情。 在處理過程當中,該方法可能會出現使人困惑的異常。 更糟糕的是,該方法能夠正常返回,但默默地計算錯誤的結果。 最糟糕的是,該方法能夠正常返回可是將某個對象置於受損狀態,在未來某個未肯定的時間在代碼中的某些不相關點處致使錯誤。 換句話說,驗證參數失敗可能致使違反故障原子性(failure atomicity )(條目 76)。github

對於公共方法和受保護方法,請使用Java文檔@throws註解來記在在違反參數值限制時將引起的異常(條目 74)。 一般,生成的異常是IllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException(條目 72)。 一旦記錄了對方法參數的限制,而且記錄了違反這些限制時將引起的異常,那麼強制執行這些限制就很簡單了。 這是一個典型的例子:數組

/**

 * Returns a BigInteger whose value is (this mod m). This method

 * differs from the remainder method in that it always returns a

 * non-negative BigInteger.

 *

 * @param m the modulus, which must be positive

 * @return this mod m

 * @throws ArithmeticException if m is less than or equal to 0

 */

public BigInteger mod(BigInteger m) {

    if (m.signum() <= 0)

        throw new ArithmeticException("Modulus <= 0: " + m);

    ... // Do the computation

}

請注意,文檔註釋沒有說「若是m爲null,mod拋出NullPointerException」,儘管該方法正是這樣作的,這是調用m.sgn()的副產品。這個異常記載在類級別文檔註釋中,用於包含的BigInteger類。類級別的註釋應用於類的全部公共方法中的全部參數。這是避免在每一個方法上分別記錄每一個NullPointerException的好方法。它能夠與@Nullable或相似的註釋結合使用,以代表某個特定參數可能爲空,但這種作法不是標準的,爲此使用了多個註解。閉包

在Java 7中添加的Objects.requireNonNull方法靈活方便,所以沒有理由再手動執行空值檢查。 若是願意,能夠指定自定義異常詳細消息。 該方法返回其輸入的值,所以能夠在使用值的同時執行空檢查:oracle

// Inline use of Java's null-checking facility

this.strategy = Objects.requireNonNull(strategy, "strategy");

你也能夠忽略返回值,並使用Objects.requireNonNull做爲知足需求的獨立空值檢查。less

在Java 9中,java.util.Objects類中添加了範圍檢查工具。 此工具包含三個方法:checkFromIndexSizecheckFromToIndexcheckIndex。 此工具不如空檢查方法靈活。 它不容許指定本身的異常詳細消息,它僅用於列表和數組索引。 它不處理閉合範圍(包含兩個端點)。 但若是它能知足你的須要,那就很方便了。ide

對於未導出的方法,做爲包的做者,控制調用方法的環境,這樣就能夠而且應該確保只傳入有效的參數值。所以,非公共方法可使用斷言檢查其參數,以下所示:工具

// Private helper function for a recursive sort

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;

    ... // Do the computation

}

本質上,這些斷言聲稱斷言條件將成立,不管其客戶端如何使用封閉包。與普通的有效性檢查不一樣,斷言若是失敗會拋出AssertionError。與普通的有效性檢查不一樣的是,除非使用-ea(或者-enableassertions)標記傳遞給java命令來啓用它們,不然它們不會產生任何效果,本質上也不會產生任何成本。有關斷言的更多信息,請參閱教程assert

檢查方法中未使用但存儲以供之後使用的參數的有效性尤其重要。例如,考慮第101頁上的靜態工廠方法,它接受一個int數組並返回數組的List視圖。若是客戶端傳入null,該方法將拋出NullPointerException,由於該方法具備顯式檢查(調用Objects.requireNonNull方法)。若是省略了該檢查,則該方法將返回對新建立的List實例的引用,該實例將在客戶端嘗試使用它時當即拋出NullPointerException。 到那時,List實例的來源可能很難肯定,這可能會使調試任務大大複雜化。

構造方法是這個原則的一個特例,你應該檢查要存儲起來供之後使用的參數的有效性。檢查構造方法參數的有效性對於防止構造對象違反類不變性(class invariants)很是重要。

你應該在執行計算以前顯式檢查方法的參數,但這一規則也有例外。 一個重要的例外是有效性檢查昂貴或不切實際的狀況,而且在進行計算的過程當中隱式執行檢查。 例如,考慮一種對對象列表進行排序的方法,例如Collections.sort(List)。 列表中的全部對象必須是可相互比較的。 在對列表進行排序的過程當中,列表中的每一個對象都將與其餘對象進行比較。 若是對象不可相互比較,則某些比較操做拋出ClassCastException異常,這正是sort方法應該執行的操做。 所以,提早檢查列表中的元素是否具備可比性是沒有意義的。 但請注意,不加選擇地依賴隱式有效性檢查會致使失敗原子性( failure atomicity)的丟失(條目 76)。

有時,計算會隱式執行必需的有效性檢查,但若是檢查失敗則會拋出錯誤的異常。 換句話說,計算因爲無效參數值而天然拋出的異常與文檔記錄方法拋出的異常不匹配。 在這些狀況下,你應該使用條目 73中描述的異常翻譯( exception translation)習慣用法將天然異常轉換爲正確的異常。

不要從本條目中推斷出對參數的任意限制都是一件好事。 相反,你應該設計一些方法,使其儘量通用。 假設方法能夠對它接受的全部參數值作一些合理的操做,那麼對參數的限制越少越好。 可是,一般狀況下,某些限制是正在實現的抽象所固有的。

總而言之,每次編寫方法或構造方法時,都應該考慮對其參數存在哪些限制。 應該記在這些限制,並在方法體的開頭使用顯式檢查來強制執行這些限制。 養成這樣作的習慣很重要。 在第一次有效性檢查失敗時,它所須要的少許工做將會獲得對應的回報。

相關文章
相關標籤/搜索