中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

螞蟻金服宮孫:guava探究系列之優雅校驗數據

發布時間:2020-08-08 20:24:02 來源:ITPUB博客 閱讀:153 作者:支付寶技術團隊 欄目:數據庫

優雅校驗數據-前置條件

前言

根據防御式編程的要求, 在日常的開發中, 總少不了對函數的各種入參做校驗, 以便保證函數能按照預期的流程執行下去. 比如各種費率的值就沒可能是負數, 如果費率出現負數, 所以數據有問題, 我們需要做的事情就是把這些有問題的數據挑出來. 自己手寫這些校驗函數未免過于繁瑣, 所幸的是我們需要的函數已經有現成的:

Guava 提供了一系列的靜態方法用于校驗函數和類的構造器是否符合預期, 并稱其為前置條件(preconditions). 如果前置條件校驗失敗, 就會拋出一個指定的異常.

前置函數特征

目前的前置校驗方法有如下特征:

須需要, 下面例子中的 checkArgument 函數可以替換成任何一個前置條件校驗函數

  1. 這些前置方法一般接受一個布爾表達式作為入參,并判斷表達是否為 true , 格式如:
Preconditions.checkArgument(a>1)// 如果表達式為false, 拋出IllegalArgumentException
  1. 除了用于判斷的布爾表達式之外, 前置方法可以接受一個額外的 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'}
  1. Guava的前置表達式還支持類似 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)

checkArgument

函數的簽名如下:

public static void checkArgument(boolean expression);

入參是一個布爾表達式, 函數校驗這個表達式是否為 true , 如果為 false , 拋出 IllegalArgumentException . 例子如下:

@Testpublic void testCheckArgument() {
    Preconditions.checkArgument(1 > 2);
}

checkNotNull

這是個泛型函數, 函數簽名如下:

public static <T> T checkNotNull(T reference);

入參是個任意類型的對象, 函數校驗這個對象是否為 null , 如果為空, 拋出 NullPointerException , 否則直接返回該對象, 所以 checkNotNull 的用法就比較有趣, 可以在調用 setter 方法前作前置校驗. 例子如下:

PreconditionTest caller = new PreconditionTest();
caller.setErrorDetail(Preconditions.checkNotNull(ErrorDetail.SC_INTERNAL_SERVER_ERROR));

checkState

函數簽名如下:

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();
  }
}

此外, 因為這兩個函數相當類似, 就不展示相應例子了.

checkElementIndex

函數簽名如下:

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()));// 運行結果:// 通過

checkPositionIndex

函數的簽名如下:

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()));// 運行結果:// 通過

checkPositionIndexes

函數的簽名如下:

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 Precondition vs Apache Common Validate

自古文無第一, 武無第二, 文人之間的口水戰總是少不了的. 沒想到這不是國人的專利, 原來國外也有文人相輕的風氣: Guava wiki 在介紹完preconditions之后, 還踩了一波競品Apache Common Validate, 認為Guava的preconditions 比Apache Common 更加清晰明了, 也更加美觀, 我個人對Apache Common Validate 了解不深, 也不好隨意置喙. 除了踩競品之外, Guava wiki 還提了兩點最佳實踐(best practice):

  1. 使用前置條件校驗的時候, 推薦每個校驗條件單獨一行, 這樣即更了然, 出問題也更方便調試.
  2. 使用前置條件校驗的時候, 盡量提供有用的錯誤信息, 這樣可以更快地定位問題.

參考資料

  • PreconditionsExplained

總結

代碼大全一書有一章是關于防御式編程的, 用于提高程序的健壯性, 主要思想是子程序應該不因傳入錯誤數據而被破壞,要保護程序免遭非法輸入數據的破壞. 而Guava的preconditions 就是實現防御式編程的有力工具呢. oh yeah!


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙岩市| 普安县| 桦南县| 章丘市| 永寿县| 无锡市| 合阳县| 广灵县| 顺平县| 田阳县| 丹凤县| 济宁市| 安国市| 平利县| 福建省| 柳林县| 永平县| 大渡口区| 汉寿县| 泸定县| 习水县| 大埔县| 措勤县| 铁岭县| 泽库县| 怀仁县| 谷城县| 方正县| 吴川市| 屯留县| 阳江市| 翼城县| 宝鸡市| 新乡县| 郴州市| 年辖:市辖区| 和田市| 吴桥县| 泗阳县| 鲜城| 达日县|