
作為程式員的你,代碼中最多的就是各種方法了,你是如何對引數進行校驗的呢?
背景
大部分的方法和建構式對傳入的引數值有一些限制,比如:常見的索引值必須是非負數,物件參考不能為空,
你應該使用清晰的檔案來標注所有的這些限制,然后在方法體開始的地方強制他們檢查,
應該在錯誤發生的時候盡快的檢查出來,這是基本原則,
如果你不這么做,當錯誤發生的時候,錯誤將不會被檢測出來,這讓定位錯誤的源頭變得更困難,
如果一個非法引數傳遞到一個方法中,在方法執行前進行了引數檢查,它將會快速失敗,并給出清晰的例外資訊,
如果方法沒有檢查引數,下面這些事情會發生,
| 程度 | 說明 |
|---|---|
| 糟糕 | 方法會在執行程序中失敗然后拋出一個不明確的例外; |
| 更糟糕 | 方法會正常回傳,但是悄悄的計算了一個錯誤的值, |
| 最糟糕 | 方法正常回傳,但是一些物件處在一個不正確的狀態,未來一個不確定的時間點在某些無關聯的點會造成一個錯誤, |
一句話總結:引數不校驗會導致原子性失敗,
推薦做法
對公共和保護方法,使用java檔案的@throws標簽來標注引數值不合法將拋出的例外,
常見的引數校驗的例外型別如下:
| 例外名稱 | 說明 |
|---|---|
| IllegalArgumentException | 非法引數 |
| IndexOutOfBoundsException | 陣列越界 |
| NullPointerException | 空指標 |
只要你已經已經在檔案中標注了方法引數的限制和違反限制會拋出的例外,限制將是一個簡單的事情,下面是一個典型的例子,
/**
*@param m 必須是正整數
*@throws ArithmeticException 如果m<=0
**/
public BigInteger mod(BigInteger m){
if(m<=0){
throw new ArithmeticException("modulus <=0: "+ m);
}
//todo 其它代碼
}
注意:
檔案注釋并沒有說, 如果m是空,mod將拋出NullPointException, 盡管這個方法確實會這樣,呼叫m.signum()的時候這個例外被標注在類級別BigInteger的檔案注釋上,類級別的注釋適用于所有的公共方法的引數,這是一個避免在每個方法單獨的檔案化標注NullPointException這種混亂的好方法,
也許可以結合@Nullable或者類似的注解來指明特殊引數可以為空,但是這個實踐并不是標準的,并且有很多注解可以用來達到這個目的,
Objects實用類
Objects.requireNonNull方法,在Java7中添加的,非常的靈活和方便,所以沒有理由手動的執行空指標檢查, 你也可以指定例外的詳細資訊,這個方法回傳自己的輸入,所以你可以在使用該值的時候執行一個空指標檢查,
//一行代碼使用java的空指標檢查
this.strategy = Objects.requireNonNull(strategy,"strategy")
如果你可以忽略回傳值,你也可以根據你的需要使用Objects.requireNonNull作為獨立的空指標檢查,
在Java9中,一個范圍檢查的方法被添加到了java.util.Objects中,包含了3個方法:
| 方法 | 說明 |
|---|---|
| checkFromIndexSize | |
| checkFromToIndex | |
| checkIndex |
這3個方法沒有空指標檢查方法靈活,它無法讓你指定自己的例外詳細資訊,它被設計用在List和Array的索引檢查上,
它也無法處理閉區間,但是只要你需要,這就是一個小便利,
Java斷言
對一個不開放的方法,你作為包的作者,控制著方法的呼叫狀況,你必須保證只有合法的引數值傳遞進去了,所以,對非公開的方法,你可以使用斷言來進行引數檢查,如下所示:
//私有幫助排序函式
private static void sort(long a[] , int offset, int length){
assert a != null ;
//更多代碼
}
本質上來講,斷言申明條件一定是true , 忽略客戶端如何使用對應的包,
跟一般的合法性檢查不同,斷言失敗的時候拋出AssertError;
跟一般的合法性檢查不同,除非你啟用他們否則斷言對你沒有任何影響和消耗,
在java命令列啟用指令:
-ea
或者
-enableassertions
更多斷言的資訊,查看java手冊的Asserts;
檢查引數的合法性非常重要,即使你的方法中沒有用到,但是存盤起來了,后面會用到,
舉個例子: 靜態工廠方法: 輸入一個 int陣列 ,回傳一個array的 list視圖, 如果客戶端傳入 null, 這個方法會拋出NPE, 因為方法會有一個直接檢查,呼叫了Objects.requireNonNull,
如果忽略檢查,方法會回傳一個參考新創建的List的實體;
而客戶端嘗試使用的時候回拋出NPE; 這個時候,原始的List實體很難決定,很大可能會復雜到變成一個除錯任務,
建構式代表了一個特殊例子的原則: 你應該檢查即將存盤稍后會用到的引數的合法性,
檢查建構式引數的合法性非常重要,它可以防止構造一個違反類的不變性的物件,
例外情況
在執行方法計算之前,你應該檢查方法引數 , 這個規則也有例外情況,
一個重要的例外情況是:合法性檢查代價非常高并且重要, 并且檢查是在執行計算的程序中執行的,
舉個例子:有一個方法對一個物件list排序,比如 Collectios.sort(list),所有的list中的物件必須是可互相比較的,在處理list比較的時候,每個物件將會跟其它的物件進行比較,
如果物件不能互相比較,其中一個或多個比較會拋出ClassCastException,這是排序方法應該做的,
所以:這里有一個小店,在開始的時候檢查串列中的元素應該是可以互相比較的,注意:修改合法性檢查會喪失原子失敗,
偶爾,一個計算執行了一個需要的合法性檢查,但是當執行檢查失敗的時候,拋出了一個錯誤的例外,換句話說,計算常常會拋出引數合法性檢查的例外,并不會匹配方法在檔案中申明的例外,這種場景下,你應該使用例外翻譯成語, 轉換自然例外為正確的例外,
這個原則并不是說武斷的限制引數是一件好事,而是說:你應該設計通用實際的方法,
假設你的方法接受所有的引陣列合而可以做一些合理事情,你的引數限制越少越好,然而,一些限制本質上在抽象類中已經被實作了,
小結
如果看完之后你只能記住一句話:每次你寫一個方法或者一個建構式,你應該思考引數的限制是否存在,你應該把限制寫在檔案中,并在方法體的開始部分確保進行了檢查,
養成這個習慣很重要,適當的作業會在第一次合法性檢查失敗的時候回饋你,

原創不易,關注誠可貴,轉發價更高!轉載請注明出處,讓我們互通有無,共同進步,歡迎溝通交流,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/39705.html
標籤:Java
上一篇:苦盡甘來,疫情被裁的我,宅家苦學2月最終吃透這630頁內容九大核心專題,識訓了3個大廠offer
下一篇:推薦幾個學習編程的網站
