关于语言不可知:没有NULL我们会做什么?

What would we do without NULL?

我曾经读过,拥有可空类型是一种绝对的邪恶。 我相信这是由创建它们的人写的一篇文章(在Ada中?)我相信这是文章

无论如何,那么如果默认情况下像C#这样的语言使用非可空类型呢? 如何替换C#或Ruby中的一些常用习语或null是可接受值的任何其他常用语言?


我认为,大多数语言都将可空性嫁接到各种类型上,而这两种概念应该是正交的,而不是直接宣称可以为空的类型是邪恶的。

例如,所有非原始Java类型(以及所有C#引用类型)都可以为空。为什么?我们可以来回走动,但最终我敢打赌答案归结为"很容易"。 Java语言没有任何内在要求广泛的可空性。 C ++参考提供了一个很好的例子,说明如何在编译器级别驱除空值。当然,C ++有一些非常丑陋的语法,而Java明确地试图缩减它,所以一些好的功能最终会出现在切割底线上。

C#2.0中的可空值类型向正确的方向迈出了一步 - 将可空性与无关类型语义分离,或者更糟糕的是,CLR实现细节 - 但它仍然缺少与引用类型相反的方法。 (代码合同很棒,但它们并没有像我们在这里讨论的那样嵌入到类型系统中。)

大量功能性或其他模糊语言从一开始就使这些概念"直接"......但如果它们被广泛使用,我们就不会进行这样的讨论......

要回答你的问题:禁止现代语言中的空白批发,就像所谓的"十亿美元错误"一样愚蠢。有一些有效的编程结构,其中空值很好:可选参数,任何类型的默认/回退计算,其中coalesce运算符导致简洁代码,与关系数据库的交互等。强制自己使用sentinel值,NaN等将是比疾病更糟糕的"治愈"。

也就是说,我暂时会同意引文中表达的观点,只要我可以详细说明以适合我自己的经验:

  • 需要空值的情况比大多数人想象的要小
  • 一旦你将空值引入库或代码路径中,除了添加它们之外,要摆脱它们要困难得多。 (所以不要让初级程序员一时兴起!)
  • 可空的bug以可变的生命周期进行扩展
  • 与#3相关:早期崩溃

  • 我们在(很少)允许空值实际需要的地方使用选项类型,并且我们有更少的模糊错误,因为任何对象引用都将保证指向适当类型的有效实例。


    Haskell是一种强大的语言,没有无效的概念。基本上,每个变量必须初始化为非空值。如果要表示"可选"变量(变量可能有值,但可能没有),可以使用特殊的"Maybe"类型。

    在Haskell中实现这个系统比在C#中更容易,因为数据在Haskell中是不可变的,所以有一个你以后填充的空引用真的没有意义。但是,在C#中,链接列表中的最后一个链接可能具有指向下一个链接的空指针,该链接在列表展开时填充。我不知道没有null类型的过程语言会是什么样子。

    此外,请注意上面的许多人似乎建议用特定于类型的逻辑"无"值(999-999-9999,"NULL"等)替换空值。这些值并没有真正解决任何问题,因为人们对空值的问题在于它们是一种特殊情况,但人们忘记为特殊情况编码。对于特定于类型的逻辑无关值,人们仍然忘记为特殊情况编写代码,但他们避免了捕获此错误的错误,这是一件坏事。


    您可以采用一个简单的规则:将所有变量初始化(默认情况下,这可以被覆盖)为由变量类定义的不可变值。对于标量,这通常是某种形式的零。对于引用,每个类将定义其"null"值是什么,并且将使用指向此值的指针初始化引用。

    这实际上是NullObject模式的语言范围实现:http://en.wikipedia.org/wiki/Null_Object_pattern
    因此它并没有真正摆脱空对象,它只是使它们不能成为必须如此处理的特殊情况。


    我想你指的是这个话题:"空参考:十亿美元的错误"


    Null不是问题,它是允许您编写访问可能为null的值的代码的语言。

    如果语言只需要检查任何指针访问权限或首先将其转换为非可空类型,则99%的null相关错误将消失。例如。在C ++中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void fun(foo *f)
    {
        f->x;                  // error: possibly null
        if (f)              
        {
            f->x;              // ok
            foo &r = *f;       // ok, convert to non-nullable type
            if (...) f = bar;  // possibly null again
            f->x;              // error
            r.x;               // ok
        }
    }

    遗憾的是,这不能对大多数语言进行改装,因为它会破坏大量代码,但对于新语言来说却是非常合理的。


    Tcl是一种语言,不仅没有null的概念,而且null本身的概念与语言的核心不一致。在tcl中我们说:'一切都是字符串'。它真正意味着什么是tcl有一个严格的值语义(它恰好默认为字符串)。

    那么tcl程序员用什么来表示"无数据"呢?大部分都是空字符串。在某些情况下,空字符串可以表示数据,那么它通常是以下之一:

  • 无论如何都要使用空字符串 - 大多数时候它对最终用户没有任何影响。

  • 使用您知道在数据流中不存在的值 - 例如字符串"_NULL_"或数字9999999或我最喜欢的NUL字节"\0"

  • 使用围绕值的数据结构 - 最简单的是列表(其他语言称为数组)。一个元素的列表表示该值存在,零元素表示null。

  • 测试变量的存在 - [info exists variable_name]

  • 值得注意的是,Tcl并不是唯一具有严格值语义的语言。 C也有严格的值语义,但值的默认语义恰好是整数而不是字符串。

    哦,差点忘了另一个:

    有些库使用数字2的变体,允许用户指定"无数据"的占位符。基本上它允许您指定默认值(如果不是,则默认值通常默认为空字符串)。


    没有NULL我们会做什么?发明吧! :-)如果你正在寻找带内指针值来表达实际上不是指针,你不必成为火箭科学家使用0。


    实际上,在任何首先允许指针或对象引用的强大编程语言中,都会出现代码能够访问没有运行任何初始化代码的指针的情况。有可能保证将这些指针初始化为某个静态值,但这似乎并不十分有用。如果一台机器有一般捕获未初始化变量(无论是指针还是别的东西)的方法,那比特殊套管空指针更好,但是我看到的最大的与空相关的错误发生在允许使用空指针进行算术的实现中。将5添加到(char *)0不应该产生到地址5的字符指针;它应该触发一个错误(如果它适合创建指向绝对地址的指针,那么应该有一些其他的方法)。


    我们会创建各种奇怪的结构来传达对象"无效"或"不存在"的信息,如其他答案所示。 null可以传达的消息。

    • 正如我在这里解释的那样,Null对象模式有其缺点。
    • 特定于域的空值。这迫使你检查魔术数字,这是不好的。
    • 集合包装器,其中空集合意味着"没有价值"。可以为空的包装器会更好,但这与检查null或使用Null对象模式没有太大区别。

    就个人而言,我会编写一些允许我使用null的C#预处理器。然后,这将映射到某个dynamic对象,只要在其上调用方法,就会抛出NullReferenceException

    早在1965年,空引用可能看起来像是一个错误。但是现在,随着各种代码分析工具向我们发出关于空引用的警告,我们不必担心这么多。从编程角度来看,null是一个非常有价值的关键字。


    我们使用其中之一

  • 鉴。一个额外的属性或标志或指示符,表示值为"null"且必须被忽略。

  • 特定领域的空白。特定值 - 在允许的域内 - 被解释为"忽略此值"。例如,社会安全号码999-99-9999可能是特定于域的空值,表示SSN未知或不适用。