最近,我参与了一个关于Java的Optional类型系统的漫长的Twitter讨论 ,该系统区分可空类型和非可空类型以及Elvis运算符 ,该运算符允许选择空值安全的成员。 后者被认为是简洁的null处理的杀手级功能,对此我强烈不同意。
我对此的看法是,如果没有允许每个类型都不能为空的类型系统(Java不久将不会发生这种情况),Elvis运算符将不利于正确性和可读性。
让我解释一下原因。
零的症结
null的问题在于它没有说明为什么缺少值
我以前已经写过这个 。 null的问题不在于它会导致异常-只是一种症状。 null的问题在于它没有说明为什么缺少该值。 是否尝试过并且失败了(例如连接到数据库),但是由于某种原因,执行仍在继续? 是否有许多值(可能是一对?),其中只能出现一个? 值是否像非强制性的用户输入一样是可选的? 或者,最后,这是一个实际的实现错误,并且该值真的应该永远不会丢失吗?
错误的代码将所有这些情况映射到同一件事:null。 因此,当NullPointerException或其他与缺失值相关的不良行为(“为什么此字段为空?”,“为什么搜索找不到该东西?”)弹出时,解决该问题的第一步是什么? 找出为什么缺少该值以及该值是否正确或实现错误。 实际上,回答该问题通常是解决方案的90%!
但是,这样做非常困难,因为null可以隐藏在任何引用类型中,并且除非进行了严格的检查(例如在构造函数和方法参数上使用Objects :: requireNonNull),否则它很容易在整个代码库中扩散。 因此,在回答为什么null在引起问题的地方出现之前,有必要先将其跟踪到其来源,这在一个足够复杂的系统中可能会花费很多时间。
因此,null的根本问题不是它引起的不当行为,而是将各种不同的关注点混为一个单一的,特别是偷偷摸摸且容易出错的概念。
猫王进入大楼
我最近与Kotlin一起玩耍,对空值处理感到惊讶,就像我以为我会从阅读它开始一样。 它不是唯一以这种方式执行的语言,而是我实际使用的语言,因此我以它为例。 但这仅仅是:一个例子。 这不是“ Kotlin比Java更好”的说法,而是“看看其他类型系统如何处理此问题”的阐述。
(如果您想进一步了解Kotlin的类型系统,我强烈建议您进行全面介绍 。)
无论如何,在这种类型的系统中,默认引用是不可为空的,并且编译器确保不会发生任何意外。 字符串始终是字符串,而不是“字符串或null”。
// declare a variable of non-nullable type `User`
val user : User = ...
// call properties (if you don't know the syntax,
// just assume these were public fields)
val userStreet : String = user.address.street
// if neither `address` not `street` return a nullable type,
// `userStreet` can never be null;
// if they would, the code would not compile because `userStreet`
// is of the non-nullable type `String`
当然,事情可能会丢失,并且可以通过追加使每种类型都为空。 对它。 从这一点开始,由于空引用,成员访问(例如调用方法)存在失败的风险。 令人敬畏的是,编译器意识到了风险,并迫使您正确地处理风险(或者大打折扣,以克服抱怨)。 做到这一点的一种方法是什么? 猫王操作员!
Elvis,写为?,区分成员所引用的引用是否为空。 如果为null,则不调用该成员,整个表达式的计算结果为null。 如果存在,则按预期方式调用该成员。
// declare a variable of the nullable type `User`
val user : User? = ...
// use Elvis to navigate properties null-safely<
val userStreet : String? = user?.address?.street
// if `user` is null, so is `userStreet`;
// `address` and `street` might return nullable types
在了解可空性的类型系统中,猫王是一种很棒的机制! 使用它,您可以表示自己知道值可能会丢失,并接受此结果作为调用结果。
同时,编译器将强制您在可能为空的引用上使用它,从而防止意外的异常。 此外,它将强行将丑陋的nullability-property传播给您将结果分配给的变量。 这迫使您随身携带可能为null值的复杂性,并且有激励您尽早摆脱它。
为什么这在Java中不起作用?
猫王只适用于非空类型
因此,如果我在Kotlin中非常喜欢Elvis,为什么不希望在Java中看到它? 因为猫王只能与区分可空类型和不可空类型的类型系统一起使用! 否则,它的作用与预期相反,并且使null问题变得更加棘手。
想一想:通过在null上调用成员可以得到NPE。 最简单的事情是什么? 挤在那里的问号,并完成它!
那是对的吗? Null不会告诉您是否允许丢失值,那么谁知道呢? 它会对调用或被调用代码产生负面影响吗? 好吧,编译器无法告诉您该代码是否可以处理null,那么又有人知道吗?
像Kotlin的类型系统可以回答这两个问题,而Java会让您猜测。 正确的选择是调查,这需要付出努力。 错误的选择是只扩散null。 如果第二种选择比今天简单得多,您认为会发生什么? 您是否希望看到或多或少缺少值的问题? 您是否期望从null引用的源到导致问题变得更长或更短的路径?
猫王让错误的选择变得容易
好的语言和好的API使正确的选择变得容易。 良好的静态类型系统中设计良好的类型可以排除运行时不应发生的情况。 这两个帐户上的Java猫王都将失败。 与其要求一种更简单的方法来处理null,不如最好从代码库或至少每种类型的public API中 消除它 。
一言以蔽之
Twitter上的大多数讨论实际上都是围绕Optional
但在这里我不再重复,因为那是另一篇文章( 我已经写过一遍, 实际上是两次 )。 相反,我想强调一个特定的论点并将其置于猫王的语境中。
有人反复指出,这是Optional
的弱点,它很容易被误操作,使用不谨慎是一种可能甚至是普遍的情况。 就个人而言,我还没有那个问题,但这听起来很合理。 我认为可以通过适度的努力(肯定比正确的空值处理更容易)来学习处理Optional
但是除非发生这种情况,否则我会明白滥用它会导致代码库变糟。
但是对于那些有这种想法的人,我想提出一个问题:您到底认为猫王的情况不会这么糟吗? 正如我在上文中指出的那样,这使可怕的选择变得非常简单! 可以说比Optional所能做到的更多。
摘要
缺乏必要的邪恶观念。 编码为null不好。 扩散可怕。
如果Java有一个类型系统可以帮助处理null并激励人们远离它,那么猫王会很棒。 ,不是。 因此,更容易在代码库中散布null而不是为丢失的值创建适当的设计,从而使针向错误的方向移动。
最后,打个招呼:如果您已经读完所有这些,并且仍然想要Elvis,因为它会使您的生活变得更加轻松 ,那么您的API可能设计不当,因为它们过度使用了null。 在那种情况下,您渴望接触Elvis的愿望正是我认为Java不应该拥有它的原因。
翻译自: https://www.javacodegeeks.com/2017/02/elvis-not-visit-java.html