螞蟻金服宮孫:guava探究系列之優雅校驗資料
優雅校驗資料-前置條件
前言
根據防禦式程式設計的要求, 在日常的開發中, 總少不了對函式的各種入參做校驗, 以便保證函式能按照預期的流程執行下去. 比如各種費率的值就沒可能是負數, 如果費率出現負數, 所以資料有問題, 我們需要做的事情就是把這些有問題的資料挑出來. 自己手寫這些校驗函式未免過於繁瑣, 所幸的是我們需要的函式已經有現成的:
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'}
-
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):
- 使用前置條件校驗的時候, 推薦每個校驗條件單獨一行, 這樣即更瞭然, 出問題也更方便除錯.
- 使用前置條件校驗的時候, 儘量提供有用的錯誤資訊, 這樣可以更快地定位問題.
參考資料
總結
程式碼大全一書有一章是關於防禦式程式設計的, 用於提高程式的健壯性, 主要思想是子程式應該不因傳入錯誤資料而被破壞,要保護程式免遭非法輸入資料的破壞. 而Guava的preconditions 就是實現防禦式程式設計的有力工具呢. oh yeah!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69904796/viewspace-2649674/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 2018年螞蟻金服前端一面總結(校招)前端
- 螞蟻金服RPC框架SOFA-RPC - 初體驗RPC框架
- 螞蟻金服RPC框架SOFA-RPC初體驗RPC框架
- [從原始碼學設計]螞蟻金服SOFARegistry 之 LocalDataServerChangeEvent及資料同步原始碼LDAServer
- 螞蟻金服楊軍:螞蟻資料分析平臺的演進及資料分析方法的應用
- 螞蟻金服 Service Mesh 大規模落地系列 - 核心篇
- 螞蟻金服 Service Mesh 大規模落地系列 - RPC 篇RPC
- 螞蟻金服資料質量治理架構與實踐架構
- [從原始碼學設計]螞蟻金服SOFARegistry之配置資訊原始碼
- 螞蟻金服 Service Mesh 大規模落地系列 - 運維篇運維
- [從原始碼學設計]螞蟻金服SOFARegistry 之 ChangeNotifier原始碼
- 螞蟻金服 Service Mesh 實踐探索
- (螞蟻金服mPaaS)統一儲存
- 螞蟻金服RPC框架結構分析RPC框架
- 螞蟻金服 Service Mesh 深度實踐
- 9.9螞蟻金服二三輪面試面試
- Dubbo服務如何優雅的校驗引數
- 螞蟻金服 Service Mesh 大規模落地系列 - 閘道器篇
- [杭州/上海/北京] 螞蟻金服資料庫平臺組招聘 Golang/Java資料庫GolangJava
- 螞蟻金服自研資料庫OceanBase如何登頂TPC-C資料庫
- [從原始碼學設計]螞蟻金服SOFARegistry之服務上線原始碼
- 招聘貼:螞蟻金服招Java研發Java
- 招聘貼:螞蟻金服招前端開發前端
- 【北京】Golang技術專家--螞蟻金服Golang
- [從原始碼學設計]螞蟻金服SOFARegistry之延遲操作原始碼
- [從原始碼學設計]螞蟻金服SOFARegistry之推拉模型原始碼模型
- SpringValid優雅校驗入參Spring
- 螞蟻金服面試經歷-前期準備面試
- 螞蟻金服 DB Mesh 的探索與實踐
- 解構螞蟻金服:巨擘崛起(附下載)
- SpringBoot中BeanValidation資料校驗與優雅處理詳解Spring BootBean
- 螞蟻金服-招資料視覺化/前端工程師(base 杭州、成都、北京、上海)視覺化前端工程師
- 互金落,螞蟻起
- 從平臺到中臺 | Elasticsearch 在螞蟻金服的實踐經驗Elasticsearch
- 螞蟻金服面出血!最新螞蟻4面(Java):CAP+資料強一致性+Lock鎖+CMS+Tomcat+RedisJavaTomcatRedis
- 螞蟻金服 Service Mesh 實踐探索 | Qcon 實錄
- 螞蟻金服!前端實習生!內推!提前批!前端
- Demo Show | 螞蟻金服 mPaaS IDEA 外掛實踐Idea