才不是什么手机评测的格式呢。

简介

option type 是一种用于消灭空指针(null pointer)的和类型(sum type)。

option type 用于表达可能不存在值的语义,在很多语言中都有相应的实现

评测对象为官方实现的 option type,而非第三方库。

主要从是否杜绝空指针和是否方便做空检查(null check)这两点评测。

C++ 的 option

C++ 17 开始有了 option 的官方实现 std::optional

引用 cppreference 的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string>
#include <iostream>
#include <optional>

// optional can be used as the return type of a factory that may fail
std::optional<std::string> create(bool b) {
if (b)
return "Godzilla";
return {};
}

int main()
{
std::cout << "create(false) returned "
<< create(false).value_or("empty") << '\n';

// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
std::cout << "create(true) returned " << *str << '\n';
}
}

天啊!居然是手动做空检查!吓得我赶紧看了一下这个类的方法和辅助函数,居然没有 map 或者 flatmap

这就是那种送到面前抄都抄错的教科书级例子。

为什么说手动做空检查是不受推荐的呢。

先忘掉 std::optional 的存在,回想以前的代码是怎么确保不会因为值不存在而崩溃的。

1
2
3
4
auto* pData = GetSomeThingMayReturnNull();
if (!pData) {
std::cout << "value is " << *pData << '\n';
}

对比上面使用 std::optional 的代码

1
2
3
if (auto str = create(true)) {
std::cout << "create(true) returned " << *str << '\n';
}

是不是做法完全一样?都是先空检查再使用值。没毛病。

但是如果做法都一样为什么还要引入 std::optional 类型?

而且引入 std::optional 对于空指针问题没有明显的改善。

程序员是人,人是会犯错误的。忘记做空检查是最常见的错误。所以才会有因为忘了做空检查导致空指针问题。

使用 std::optional 还是得手动做空检查,那跟直接使用 nullptr 表达空值有什么区别?

也就是使用 std::optional 没有办法杜绝空指针,有出错的可能,除非能证明所有对 std::optional 的使用前都做了空检查。

那是不是没有办法杜绝空指针?

答案是否定的。在强类型语言如 Haskell 中会强迫用户检测,否则编译失败。从而杜绝了运行时空指针。

所以 std::optional 错在没有强制做空检查,没有强制做空检查的原因是编译器没有给予支持而空检查和取值操作是分开的两步操作,而不是一步原子操作。

评测结果:不推荐使用

Java 的 option

Java 8 也引入了 option type ,类名也是 Optional

引用介绍 optional 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class OptionalDemo {
 
  public static void main(String[] args) {
    //创建Optional实例,也可以通过方法返回值得到。
    Optional<String> name = Optional.of("Sanaulla");
 
    //创建没有值的Optional实例,例如值为'null'
    Optional empty = Optional.ofNullable(null);
 
    //isPresent方法用来检查Optional实例是否有值。
    if (name.isPresent()) {
      //调用get()返回Optional值。
      System.out.println(name.get());
    }
 
    try {
      //在Optional实例上调用get()抛出NoSuchElementException。
      System.out.println(empty.get());
    } catch (NoSuchElementException ex) {
      System.out.println(ex.getMessage());
    }
 
    //ifPresent方法接受lambda表达式参数。
    //如果Optional值不为空,lambda表达式会处理并在其上执行操作。
    name.ifPresent((value) -> {
      System.out.println("The length of the value is: " + value.length());
    });
 
    //如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
    System.out.println(empty.orElse("There is no value present!"));
    System.out.println(name.orElse("There is some value!"));
 
    //orElseGet与orElse类似,区别在于传入的默认值。
    //orElseGet接受lambda表达式生成默认值。
    System.out.println(empty.orElseGet(() -> "Default Value"));
    System.out.println(name.orElseGet(() -> "Default Value"));
 
    try {
      //orElseThrow与orElse方法类似,区别在于返回值。
      //orElseThrow抛出由传入的lambda表达式/方法生成异常。
      empty.orElseThrow(ValueAbsentException::new);
    } catch (Throwable ex) {
      System.out.println(ex.getMessage());
    }
 
    //map方法通过传入的lambda表达式修改Optonal实例默认值。
    //lambda表达式返回值会包装为Optional实例。
    Optional<String> upperName = name.map((value) -> value.toUpperCase());
    System.out.println(upperName.orElse("No value found"));
 
    //flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
    //map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
    //但是flatMap方法的lambda返回值总是Optional类型。
    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
    System.out.println(upperName.orElse("No value found"));
 
    //filter方法检查Optiona值是否满足给定条件。
    //如果满足返回Optional实例值,否则返回空Optional。
    Optional<String> longName = name.filter((value) -> value.length() > 6);
    System.out.println(longName.orElse("The name is less than 6 characters"));
 
    //另一个示例,Optional值不满足给定条件。
    Optional<String> anotherName = Optional.of("Sana");
    Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
    System.out.println(shortName.orElse("The name is less than 6 characters"));
 
  }
 
}

Java 8 的 Optionalmapflatmap 。很好。

最大的败笔在于提供了 get() 方法。

在没有提供之前可以保证代码不会有空指针。

提供 get() 后,就必须要手动做空检查。

这就跟 C++ 的 std::optional 一样。

根据墨菲定律,可能出错的终将会出错。所以很可能出现直接使用 get() 而忘了做空检查。

如果 get() 抛出的异常是编译时异常还好,这样不做空检查就会编译失败。

但是 get() 抛出的异常 NoSuchElementException 是运行时异常,简直就是变相的 null

听说 Optional可以是空指针

讲个笑话。

1
2
3
Optional<Customer> customer = customerService.retriveCustomerById(22492722);

customer.map(Customer::getName).orElse("Hello world!");

「retriveCustomerById返回null没有编译错误」

哇!这就意味着你要先检查 Optional 是不是空,在检查里面有没有值。

简直是帮倒忙,不解决原有的问题还引入了新的问题。

评测结果:凑合使用,不要使用 get() ,更不要使用 orElse(null)

没有空指针的未来

在有了正确的 option type 后,你离没有空指针的代码只有一个语法糖的距离。

终有一天我们会跨入无空指针的时代。

到时回看这时的代码。

那一天,人类终于回想起了,曾经一度被空指针所支配的恐怖,还有被囚禁于空检查大括号中的那份屈辱。