代码整洁之道-参数检查和 null

原创文章,转载请注明出处

引言
代码质量的好坏,本身是一个比较难量化的标准,现在应该很少有公司再以一个程序员产出的代码行数作为标准了。怎样来评判代码的好坏其实是一项比较麻烦的事情,每个人的着眼点不同,相应的代码就各式各样。但是根据自身经验来看,当然也是我比较信奉的一点,就是在目前的开发条件下,代码的组织结构是比执行效率需要优先考虑的事情。

本篇的主旨是整理一些在开发中能够实际提升代码质量的工具和技巧。

Apache-Commons和Guava

相信有过一些开发经验的人对这两个工具都不会太陌生,他们提供的许多类能够大大提高开发效率,并且使代码更加整洁。个人更喜欢 Guava 的设计,工作中往往是混用,当然要理解每种工具的适用场景,才能做到高效和简洁。

1. 参数检查

刚参加工作的时候,印象非常深刻的是当我想检查一些参数是否符合要求时,要写许多冗余的代码。
例如在一个 Service 的入口处想要检查一个 String 类型的参数是否为空,这是我最初的写法:

public void doSomething(String str) throws Exception {
    if (str == null || str.length() == 0) {
        throw new Exception("str should not be blank");
    }
}

乍看之下确实没有什么问题,但是当参数个数增加的时候事情就变得麻烦了,同样的代码逻辑需要重复好几遍,最致命的就是导致可读性下降。还有就是当str是连续的空格时也绕过了检查。
Guava中的 Preconditions类就是专门解决参数检查问题的,先来看看使用了 Preconditions 以后的代码变成了什么样子:

public void doSomething(String str) throws Exception {
    Preconditions.checkArgument(StringUtils.isNotBlank(str), "str should not be blank");
}

很多教程中都强烈建议将 Preconditions 静态导入,这样代码会更加简洁。通过比较两处代码不难看出:

  1. 代码的可读性提高了
  2. 替代掉了 ifthrow 语句,提升了可维护性
  3. 省略了判断条件,代码更健壮

关于2,3点可能需要说明一下。记得 Boss 之前有跟我聊过一次,有一点是说自己写的代码里应该尽量少出现 if 这样的控制结构,当时理解并不是太深刻,但是随着写的代码越来越多,渐渐意识到这其实是思想的一种转变。
在一个方法之中,出现的控制结构越多,思维还停留在面向过程编程的可能性就越大(虽然现在大多数程序员都不愿意承认这点)。就以上面的代码为例,判断字符串是否为空的这项功能并不是方法的主要业务,那么就应该由专门做这项功能的类(对象)来进行处理,所以我们交给了 StringUtils ,同样的,根据参数是否满足要求来抛出异常的功能我们交给了 Preconditions
顺带值得一提的是 Preconditions 中还有许多检查参数的方法:checkNotNull(), checkState()等等,这些方法都是快速失败的。

2. 思考 null 想表达的意义

每次自己写的程序报了空指针错误,我都会提醒自己更加谨慎,因为这其实是在说:
你考虑的不够周全
许多时候NPE 所代表的问题是我们并没有思考清楚 null在这里是想表达什么意思,还是先看一个例子:
我们现在想从一个 List>结构的链表中逐个解析每个字段,他的数据大概是这个样子:

[
    {
        "code":"123",
        "name":"yzhang",
        "nickname":"Rocket"
    },
    {
        "code":"234",
        "name":"Kevin",
        "nickname":"Bee"
    }
]

但是现在有一些规则:

  1. 每个 map 中的 code 不能为空,否则跳过
  2. name 可以为空,但是不能显示为“null”
  3. nickname 如果为空,则使用默认值“Avenger”

下面是一段实现代码:

for (int i = 0, size = personList.size(); i < size; i++) {
    Map person = personList.get(i);
    if (MapUtils.isNotEmpty(person)) {
        String code = person.get("code");
        String name = person.get("name");
        String nickname = person.get("nickname");
        if (StringUtils.isBlank(code)) continue;
        if (name == null) name = "";
        if (nickname == null) nickname = "Avenger";
        // do something else
    }
}

这段代码已经考虑了一些可能出现 NPE 的情况,但是出现了和 [1] 中相同的问题,有大量的if语句,对于后期维护非常不利。
Guava 中提供了Optional类来强制开发人员思考 null所表达的意义,正如 Optional 表达的意思,当一个变量可能会出现 NPE 时我们就应该用 Optional 来处理它(Java 8的util包已经加入了 Optional 类,接口命名上有稍许不同)
看看改进后的代码:

for (int i = 0, size = personList.size(); i < size; i++) {
    Map person = Optional.fromNullable(personList.get(i)).or(Maps.newHashMap());
    String code = person.get("code");
    if (StringUtils.isBlank(code)) continue;
    String nickname = Optional.fromNullable(person.get("nickname")).or("Avenger");
    String name = Optional.fromNullable(person.get("name")).or("");
    // do something else
}

同样的,我们减少了很多 if结构,代码也更便于阅读(比起与一堆null做比较,fromNullableor 显然更清晰)。
还有很重要的一点,即使在第一段代码中,也有不少人会忘记写if (MapUtils.isNotEmpty(person)),因为这没有在三点要求以内。
关于 Optional的详细使用可以参考 Guava 的官方文档,值得注意的是Optional.of(T t)也是快速失败的。建议在一个对象有不确定性的时候都考虑使用 Optional 来处理,特别是在遍历 MapList 之类的集合时,因为你永远不知道调用者什么时候会给你一个null

你可能感兴趣的:(代码整洁之道-参数检查和 null)