文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

蚂蚁金服宫孙:guava探究系列之优雅校验数据

2024-04-02 19:55

关注

优雅校验数据-前置条件

前言

根据防御式编程的要求, 在日常的开发中, 总少不了对函数的各种入参做校验, 以便保证函数能按照预期的流程执行下去. 比如各种费率的值就没可能是负数, 如果费率出现负数, 所以数据有问题, 我们需要做的事情就是把这些有问题的数据挑出来. 自己手写这些校验函数未免过于繁琐, 所幸的是我们需要的函数已经有现成的:

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 {        
    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);
        }
    }        
    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. 使用前置条件校验的时候, 尽量提供有用的错误信息, 这样可以更快地定位问题.

参考资料

总结

代码大全一书有一章是关于防御式编程的, 用于提高程序的健壮性, 主要思想是子程序应该不因传入错误数据而被破坏,要保护程序免遭非法输入数据的破坏. 而Guava的preconditions 就是实现防御式编程的有力工具呢. oh yeah!


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯