您好,登錄后才能下訂單哦!
根據防御式編程的要求, 在日常的開發中, 總少不了對函數的各種入參做校驗, 以便保證函數能按照預期的流程執行下去. 比如各種費率的值就沒可能是負數, 如果費率出現負數, 所以數據有問題, 我們需要做的事情就是把這些有問題的數據挑出來. 自己手寫這些校驗函數未免過于繁瑣, 所幸的是我們需要的函數已經有現成的:
Guava 提供了一系列的靜態方法用于校驗函數和類的構造器是否符合預期, 并稱其為前置條件(preconditions). 如果前置條件校驗失敗, 就會拋出一個指定的異常.
目前的前置校驗方法有如下特征:
須需要, 下面例子中的
checkArgument
函數可以替換成任何一個前置條件校驗函數
true
, 格式如:
Preconditions.checkArgument(a>1)// 如果表達式為false, 拋出IllegalArgumentException
Object
作為入參, 在拋出異常的時候, 把
Object.toString()
作為異常信息, 如:
public enum ErrorDetail { SC_NOT_FOUND("404", "Resource could not be fount"); // 省略部分內容 @Override public String toString() { return "ErrorDetail{" + "code='" + code + '\'' + ", description='" + description + '\'' + '}'; } }@Testpublic void testCheckArgument() { Preconditions.checkArgument(1 > 2, ErrorDetail.SC_NOT_FOUND); }// 結果如下:// java.lang.IllegalArgumentException: ErrorDetail{code='404', description='Resource could not be fount'}
printf
函數那樣的格式化輸出錯誤信息, 只不過出于兼容性和性能的考慮, 只支持使用
%s
指示符格式化字符串, 不支持其他類型. 如:
int i=-1; checkArgument(i >= 0, "Argument was %s but expected nonnegative", i);// 結果如下:// java.lang.IllegalArgumentException: Argument was -1 but expected nonnegative
須注意的是, 下面介紹的
checkArgument
,
checkArgument
,
checkState
函數都有三個對應的重載函數,分別對應前文所述的三種特征, 下文不會三種函數都介紹, 只介紹標準格式的前置條件函數. 以
checkArgument
函數為例, 三個重載函數分別是(忽略函數體):
public static void checkArgument(boolean expression);public static void checkArgument(boolean expression, @Nullable Object errorMessage);public static void checkArgument(boolean expression,@Nullable String errorMessageTemplate,@Nullable Object... errorMessageArgs)
函數的簽名如下:
public static void checkArgument(boolean expression);
入參是一個布爾表達式, 函數校驗這個表達式是否為
true
, 如果為
false
, 拋出
IllegalArgumentException
. 例子如下:
@Testpublic void testCheckArgument() { Preconditions.checkArgument(1 > 2); }
這是個泛型函數, 函數簽名如下:
public static <T> T checkNotNull(T reference);
入參是個任意類型的對象, 函數校驗這個對象是否為
null
, 如果為空, 拋出
NullPointerException
, 否則直接返回該對象, 所以
checkNotNull
的用法就比較有趣, 可以在調用
setter
方法前作前置校驗. 例子如下:
PreconditionTest caller = new PreconditionTest(); caller.setErrorDetail(Preconditions.checkNotNull(ErrorDetail.SC_INTERNAL_SERVER_ERROR));
函數簽名如下:
public static void checkState(boolean expression);
看著這個函數, 我個人感覺很奇怪: 這個函數和
checkNotNull
函數功能非常相似, 實現也基本一樣, 都是判斷表達式是否為
true
, 只是拋出的異常不一樣而已, 是否有必要開發這個函數. 兩個函數的實現如下:
public static void checkArgument(boolean expression) { if (!expression) { throw new IllegalArgumentException(); } }public static void checkState(boolean expression) { if (!expression) { throw new IllegalStateException(); } }
此外, 因為這兩個函數相當類似, 就不展示相應例子了.
函數簽名如下:
public static int checkElementIndex(int index, int size);
這個函數用于判斷指定數組, 列表, 字符串的下標是否越界,
index
是下標,
size
是數組, 列表或字符串的長度, 下標的有效范圍是
[0,數組長度)
即
0<=index<size
. 如果數組下標越界(即
index
<0 或者
index
>=
size
), 那么拋出
IndexOutOfBoundsException
異常, 否則返回數組的下標, 也就是
index
. 例子如下:
Preconditions.checkElementIndex("test".length(), "test".length());// 運行結果:// 拋出異常: java.lang.IndexOutOfBoundsException: index (4) must be less than size (4)Assert.assertEquals(3, Preconditions.checkElementIndex("test".length() - 1, "test".length()));// 運行結果:// 通過
函數的簽名如下:
public static int checkPositionIndex(int index, int size);
這個函數和
checkElementIndex
非常類似, 連Guava wiki的說明也基本一致(只有一個單詞不同), 除了一點,
checkElementIndex
函數的下標有效范圍是
[0, 數組長度)
, 而
checkPositionIndex
函數的下標有有效范圍是
[0, 數組長度]
, 即
0<=index<=size
. 例子如下:
Preconditions.checkPositionIndex("test".length() + 1, "test".length());// 運行結果:// 拋出異常: java.lang.IndexOutOfBoundsException: index (5) must be less than size (4)Assert.assertEquals(4, Preconditions.checkPositionIndex("test".length(), "test".length()));// 運行結果:// 通過
函數的簽名如下:
public static void checkPositionIndexes(int start, int end, int size);
這個函數是用于判斷
[start,end]
這個范圍是否是個有效范圍, 即
[start, end]
是否在
[0, size]
范圍內(如果
[start, end]
和
[0, size]
相同, 也認為在范圍內), 如果不在, 則拋出
IndexOutOfBoundsException
異常. 例子如下:
Preconditions.checkPositionIndexes(1, 3, 2);// 運行結果:// 拋出異常: java.lang.IndexOutOfBoundsException: end index (3) must not be greater than size (2)Preconditions.checkPositionIndexes(0, 2, 2);// 運行結果:// 校驗通過
前置條件在檢驗條件不成交的時候拋的異常類型雖說是合情合理(比如,
checkArgument
函數拋出
IllegalArgumentException
), 但是對于業務系統來說, 你拋出個
IllegalArgumentException
或者
NullPointerException
, 接口調用方對于這個異常摸不著頭腦, 雖說只是正常的數據問題, 還是很容易覺得接口提供方服務出了問題, 甚至還會被質疑技術不過硬. 咱們又不是底層組件, 拋個
NPE
, 著實是不成體統. 基于各種有的沒的的原因, 我們的業務系統在使用前置條件的時候進行了封裝, 將前置條件拋出的異常進行了轉換, 換成正常的業務異常, 提供完整的異常信息, 代碼如下:
// 封裝代碼:public final class AssertUtils { /** * 檢查條件表達式是否為真 * * @param expression 條件表達式 * @param errDetailEnum 錯誤碼 * @param msgTemplate 錯誤消息模板 * @param vars 占位符對應變量 * @throws BkmpException 條件表達式結果為假 */ public static void checkArgument(boolean expression, ErrDetailEnum errDetailEnum, String msgTemplate, Object... vars) { try { Preconditions.checkArgument(expression); } catch (IllegalArgumentException e) { throw new BkmpException(errDetailEnum, msgTemplate, vars); } } /** * 檢查條件表達式是否為假 * * @param expression 條件表達式 * @param errDetailEnum 錯誤碼 * @param msgTemplate 錯誤消息模板 * @param vars 占位符對應變量 * @throws BkmpException 條件表達式結果為假 */ public static void checkArgumentNotTrue(boolean expression, ErrDetailEnum errDetailEnum, String msgTemplate, Object... vars) { try { Preconditions.checkArgument(!expression); } catch (IllegalArgumentException e) { throw new BkmpException(errDetailEnum, msgTemplate, vars); } } }// 省略其他部分的封裝// 調用例子:AssertUtils.checkArgument(merchantEntity.exist(), ErrDetailEnum.DATA_NOT_EXIT, "商戶不存在");
自古文無第一, 武無第二, 文人之間的口水戰總是少不了的. 沒想到這不是國人的專利, 原來國外也有文人相輕的風氣: Guava wiki 在介紹完preconditions之后, 還踩了一波競品Apache Common Validate, 認為Guava的preconditions 比Apache Common 更加清晰明了, 也更加美觀, 我個人對Apache Common Validate 了解不深, 也不好隨意置喙. 除了踩競品之外, Guava wiki 還提了兩點最佳實踐(best practice):
代碼大全一書有一章是關于防御式編程的, 用于提高程序的健壯性, 主要思想是子程序應該不因傳入錯誤數據而被破壞,要保護程序免遭非法輸入數據的破壞. 而Guava的preconditions 就是實現防御式編程的有力工具呢. oh yeah!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。