正确使用null
有不同的使用方法null
。最常见且语义上正确的方法是在您可能有或没有单个值时使用它。在这种情况下,值等于null
或有意义,例如数据库中的记录或其他内容。
在这种情况下,您通常会像这样(以伪代码)使用它:
if (value is null) {
doSomethingAboutIt();
return;
}
doSomethingUseful(value);
问题
这是一个很大的问题。问题在于,到您调用doSomethingUseful
该值时,可能尚未检查null
!如果不是,则程序可能会崩溃。而且用户甚至看不到任何好的错误消息,而留下诸如“可怕的错误:想要的值却为空!”之类的信息。(更新后:尽管信息错误可能更少,例如Segmentation fault. Core dumped.
,或更糟的是,在某些情况下,没有错误和对null的错误操作)
忘记写检查null
和处理null
情况是一个非常常见的错误。这就是为什么发明的托尼·霍尔(Tony Hoare)null
在2009年的QCon伦敦软件大会上说他在1965年犯了数十亿美元的错误的原因:https:
//www.infoq.com/presentations/Null-References-The-Billion-Dollar-托尼·霍尔
避免问题
一些技术和语言使检查null
变得不可能以不同的方式被遗忘,从而减少了错误的数量。
例如,Haskell具有Maybe
monad而不是null。假设这DatabaseRecord
是用户定义的类型。在Haskell中,type的值Maybe DatabaseRecord
可以等于Just <somevalue>
或可以等于Nothing
。然后,您可以以不同的方式使用它,但是无论如何使用,都无法在Nothing
不知情的情况下对其进行某些操作。
例如,此函数称为zeroAsDefault
return x
for Just x
和0
for Nothing
:
zeroAsDefault :: Maybe Int -> Int
zeroAsDefault mx = case mx of
Nothing -> 0
Just x -> x
Christian Hackl说C ++ 17和Scala有自己的方式。因此,您可能想尝试找出您的语言是否具有类似功能并加以使用。
空值仍在广泛使用
如果您没有更好的选择,那么使用null
就可以了。只是继续提防它。无论如何,函数中的类型声明将对您有所帮助。
同样,这听起来可能不是很进步,但是您应该检查您的同事是否要使用它null
或其他东西。他们可能很保守,出于某些原因可能不想使用新的数据结构。例如,支持语言的较早版本。这些事情应在项目的编码标准中声明,并与团队进行适当讨论。
根据您的建议
您建议使用一个单独的布尔字段。但是无论如何您都必须对其进行检查,但仍然可能忘记对其进行检查。因此,这里没有任何胜利。如果您甚至忘记了其他事情,例如每次都更新两个值,那就更糟了。如果忘记检查的问题null
没有解决,那就没有意义了。避免null
是困难的,您不应以使情况变得更糟的方式来避免。
如何不使用null
最后,有null
不正确使用的常见方法。一种这样的方法是使用它代替空的数据结构,例如数组和字符串。空数组是与其他数组一样的适当数组!对于可以容纳多个值的数据结构来说,它可以为空(即长度为0)几乎总是重要且有用的。
从代数的角度来看,字符串的空字符串非常类似于数字的0,即身份:
a+0=a
concat(str, '')=str
空字符串通常会使字符串变成一个半形体:https :
//en.wikipedia.org/wiki/Monoid
如果您不明白,那对您来说就不那么重要了。
现在,让我们来看一下为什么这个例子对编程很重要:
for (element in array) {
doSomething(element);
}
如果我们在此处传递一个空数组,代码将正常工作。它什么也不会做。但是,如果我们在null
此处传递a ,则可能会崩溃,并显示诸如“无法循环通过null,抱歉”之类的错误。我们可以将其包裹起来,if
但是不太干净,您可能会忘记检查它
如何处理null
什么doSomethingAboutIt()
应该做,特别是它是否应该抛出一个异常是另一个复杂的问题。简而言之,它取决于null
给定任务的输入值是否可接受以及响应期望值。异常是意外事件。我将不进一步讨论该主题。这个答案已经很久了。
std::optional
或Option
。在其他语言中,您可能必须自己构建一个适当的机制,或者您可能实际上诉诸于null
类似机制,因为它更惯用了。