这是Jerry 2021年的第 10 篇文章,也是汪子熙公众号总共第 281 篇原创文章。

今天是2021年1月17日,星期日,腊月初五。

Jerry之前收到CSDN社区赠送的新年礼物,一本台历:《了不起的程序员》,

526c24a5b73e17520042f37675c58216.png

其中1月16日,17日两天的篇幅,都在介绍托尼·霍尔(Tony Hoare), 计算机科学家,因程序设计语言定义与设计方面的杰出贡献获得1980年的图灵奖。快速排序算法的发明者。

d87893172e9fb6e27c3d6e59fb1647eb.png

如果想查看用ABAP实现的8种排序算法的源代码,可以查看Jerry之前的文章:

本文不会讨论霍尔发明的快速排序算法,而是介绍另一个来自霍尔,如今仍然被程序员在编程语言中广泛使用的一个设计:null引用。

94425a9f20a8edb6ab83c2d66316ee26.png

null引用被霍尔称为"十亿美元错误",是霍尔1965年设计ALGOL W语言时提出的。

《Java实战》中提到,在Java程序开发中使用null会带来理论和实际操作上的种种问题:它是错误之源

会使你的代码膨胀

自身毫无意义

破坏了Java的哲学

在Java的类型系统上开了口子

霍尔的名言:我把它叫做我的“十亿美元错误”,就是在1965年发明了空引用...... 我无法抵挡放进一个空引用的诱惑,仅仅是因为实现起来非常容易。

引入了空引用的编程语言,在访问引用之前,需要显式检查引用是否有效。

Java

下图第46行代码定义的print方法,输入参数是一个类型为Integer的引用。在调用引用之前,需要先判断其是否是空引用,否则程序执行时就会出现运行时异常。

fb4fe5445c911e78ece12bf87ebca503.png

ABAP

使用CHECK X IS NOT INITIAL进行防御,如果X为空引用,则不会执行CHECK语句的下一条语句。

55c7d801dc0bbea5c72b5777bad28efb.png

严谨的德国人,在霍尔教授null引用的基础上,又设计出IS BOUND, IS NOT INITIAL和IS ASSIGNED这几种判断逻辑:

JavaScript

第10行的print方法内部,用&&操作符的短路逻辑(short-circuit)特性来实现空引用的检测:如果传入的oPrinter是空引用,则不会执行&&后面的print调用。

48f8b76dadbbfcb80b84f048eec3071d.png

而TypeScript提供的可选链(Optional Chaining),则可以在语言层面优雅地避免这个问题。

下面的TypeScript代码,使用问号构造了一个可选链。如果a为空,则表达式a?.b直接返回undefined给变量val,而不会试图去执行a.b

a7f0462dc20447c6baeeacae5180dfd2.png

上图TypeScript代码,编译之后生成的JavaScript代码如下图所示,我们可以把TypeScript的可选链看成JavaScript用三元表达式实现的语法糖。

ccc3f20e1d640689c24008d33eaaf8b6.png

为了减轻Java程序员每次使用引用之前,显式进行非空检查的工作量,Java 8引入了一个新的工具类:Optional.

Optional仅仅是一个不含任何业务逻辑的包裹类,其value字段指向了真正的业务类。

e2ac5ffc5406ed8c33a2f68b037d985c.png

下图是一个使用Optional工具类的例子,第11行的filter方法,传入的是一个通过Lambda Function实现的过滤条件。这行语句的语义是,对anotherName包含的字符串,进行过滤操作,检查another实例的value字段存储的引用,是否满足过滤条件(字符串长度小于6):

6987fce00b28c8bc4aa07c3f7f55edcd.png

Optional.filter方法,无论过滤条件是否满足,返回的类型均为Optional,便于链式调用。

我第10行传入Optional对象的字符串,显然长度远远大于6,所以filter方法返回一个新的Optional对象,其value字段为null.

对于filter调用返回的Optional对象,我们可以继续调用orElse,设置一个默认值。下图第14行用orElse实现的逻辑,语义是:如果shortName包裹的value字段为空,则返回orElse方法传入的默认值。

a4c87d4570d7de774d23ac9e0d0a20e1.png

Java 8的Optional工具类并不像TypeScript的可选链一样,后者是语言层面提供的特性,而Optional仅仅是开发包里的一个工具类。

比如Optional的静态方法of,其实现仅仅是新建一个Optional对象,去包裹传入的value引用:

577593fba5460e316f14f88db0e7f7ff.png

orElse方法,内部实现也是一个简单的三元表达式。

98cac9a370ca40e20145a9f803fd2d05.png

看这样一个极端的例子:

Outer类有一个字段nested,类型为Nested类。Nested类有一个字段inner,类型为Inner类。Inner类包含了字段foo,类型为String,值为Jerry:

6f844027f4994094fdee19e358d8d40c.png

如果想从Outer类的实例出发,写一段比较健壮的代码,打印出深藏在Inner类里的foo字段,常规的写法和使用Optional的写法分别位于下图test1和test2方法,大家可以比较下,更喜欢哪一种?

0d018f340a8e0b45d63c54a7f3cc7273.png

值得一提的是,类似Java Optional.orElse方法,在ABAP里也存在基于语言层面的支持。

下图是ABAP 740的新语法:

7ed0c8e5450777102451af5841fcc697.png

上面的新语法,翻译成传统的ABAP代码如下:

7c19abbcbd5fff5a14afce734d1a044d.png

由此可见,新的ABAP内表读取的语法比较简洁,能少写3行代码。

但是新语法有一个缺陷:如果it_data内表,不存在object_ext的值为cl_crm_prodil_bo_names=>gc_prod_root的记录,此时程序执行会被终止,抛出异常CX_SY_ITAB_LINE_NOT_FOUND:

1e0e456068cb3106b6408bd7239597bf.png

当然针对这种情况,ABAP也有对应的解决方案。

下图测试代码第17行会抛出异常,而19行不会。从语义上容易理解:如果内表lt_data里不存在name为Spring2的记录,则返回开发者使用DEFAULT VALUE关键字指定的一个结构,作为默认值。

a8b3f7712da301b36acf824320e2184d.png

第19行执行完毕后,结构ls3的name字段为SpringInvalid, value为999.

本文从霍尔教授1965年提出的null引用作为切入点,向大家分享了Jerry工作中同空引用打交道的一些经历,感谢阅读。

ABAP专题

更多Jerry的原创文章,尽在:"汪子熙":

d8af0209d43a66cec2d50b50e54ef10c.png

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐