前言

看到好多文章都是推荐采用Optinal的,而经常我遇到问题的时候就想:如果设计成optional的话就不会忽略这种NullPointException错误了。然而,optional并不是想用就随便用的。今天花了10分钟追踪一个bug,根源就是optional滥用。

问题描述

API返回失败,没有描述原因。看着蛋疼,因为公开的API不方便返回错误详情。于是查log,发现错误日志的message为:No value present。没搞清楚这个错误信息是哪一层跑出来的。需要进一步跟踪。A=>B=>C=>D,一直追踪到C层才找到问题。

问题代码如下:

1
2
3
4
public FieldBuilder withSubcategoryId(Optional<String> id) {
this.id = id.get();
return this;
}

这是一个创建工厂类,负责创建一个可以使用对象。所有的字段都采用了Opetional的包裹。这个是对象,理应不包含业务逻辑,应该没有错误异常。如果有异常应该显式的throws出来,不然这个非检查性异常将在出现bug的时候难以定位。而这里确实有一个异常没有捕获,而且也不能保证不会发生,甚至就是这里引起的bug:java.util.Optional#get

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* If a value is present in this {@code Optional}, returns the value,
* otherwise throws {@code NoSuchElementException}.
*
* @return the non-null value held by this {@code Optional}
* @throws NoSuchElementException if there is no value present
*
* @see Optional#isPresent()
*/
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

虽然没有显式的抛出异常,但在javadoc中写清楚了会出现的问题。而我们这些新手则没有认真看文档就想当然的采用了。以为当内容为null的时候get出来的还是null

Find Uage找这个Builder的用法发现:

1
new FieldBuilder().withSubcategoryId(Optional.ofNullable(entity.getSubcategoryId()))

这里直接使用了Optional.OfNullable。然而,我们知道在下一步中会调用get,get的时候回判断是否是nullnull会抛出异常。这简直就是自己挖坑,写一个条件抛异常,而传参数又专门去符合这个条件。前面也没有校验,外面也没有捕获异常,最终导致异常直接一路抛出到API外层去了。

结论

Optional不要滥用,Optional不是安全的随便用的,Optional用的时候记得捕获异常。