我曾经读到,使用可空类型是一种绝对的恶习。我相信这是由创造它们的人(在Ada语言中?)所写的一篇文章。我相信这篇文章就是
无论如何,那么如果像C#这样的语言默认使用非空类型,你将如何替换一些常见的C#或Ruby等任何其他常见语言中的惯用法,其中null
是可接受的值?
我曾经读到,使用可空类型是一种绝对的恶习。我相信这是由创造它们的人(在Ada语言中?)所写的一篇文章。我相信这篇文章就是
无论如何,那么如果像C#这样的语言默认使用非空类型,你将如何替换一些常见的C#或Ruby等任何其他常见语言中的惯用法,其中null
是可接受的值?
与其斩钉截铁地宣称可为空类型是邪恶的,我认为:大多数语言将可空性附加到整个类型,而这两个概念实际上应该是正交的。
例如,所有非原始Java类型(以及所有C#引用类型)都可为空。为什么?我们可以来回讨论,但最终我打赌答案归结为“这很容易”。Java语言本身并没有要求广泛使用可空性。C++引用提供了一个很好的例子,展示了如何在编译器级别消除nulls。当然,C++具有更多丑陋的语法,而Java明确试图遏制这些语法,因此一些好的特性和坏的特性一起被砍掉。
C# 2.0中的可空值类型迈出了正确的一步——将nullability与不相关类型语义或更糟糕的CLR实现细节分离开来——但它仍然缺少一种处理引用类型相反的方式。(代码契约很好,但它们没有嵌入到我们正在讨论的类型系统中。)
许多功能性或其他晦涩的语言从一开始就理解了这些概念……但如果它们得到广泛使用,我们就不会进行这种讨论了……
回答您的问题:在现代语言中,全面禁止nulls与所谓的“十亿美元错误”一样愚蠢。有些有效的编程结构需要nulls:可选参数、任何默认/回退计算,在其中合并运算符会导致简洁的代码,与关系数据库的交互等等。强迫自己使用哨兵值、NaN等将是比疾病更严重的“治疗方法”。
话虽如此,我暂时同意引用中表达的观点,只要我能够详细阐述我的经验:
我们将使用选项类型来表示那些允许为空值的(非常少见的)情况,并且由于任何对象引用都保证指向适当类型的有效实例,因此我们将减少很多难以捉摸的错误。
match opt with | Some(value) -> do_something_with value | None -> oops_its_null
。如果您尝试执行 do_something_with opt
,则会收到编译时类型错误。 - Jason OrendorffHaskell是一种强大的语言,没有空值的概念。基本上,每个变量都必须初始化为非空值。如果你想表示一个“可选”的变量(变量可能有一个值,但也可能没有),你可以使用特殊的“Maybe”类型。
在Haskell中实现这个系统比在C#中容易,因为在Haskell中数据是不可变的,所以在稍后填充空引用没有意义。然而,在C#中,链表中的最后一个链接可能具有指向下一个链接的空指针,在列表扩展时会被填充。我不知道一个没有空类型的过程化语言会是什么样子。
此外,需要注意的是,许多人似乎建议用类型特定的逻辑“无值”值(999-999-9999,“NULL”等)替换空值。这些值并没有真正解决任何问题,因为人们对空值的问题是它们是一个特殊情况,但人们忘记编写特殊情况的代码。使用类型特定的逻辑无值值,人们仍然会忘记编写特殊情况的代码,但他们避免了捕捉此错误的错误,这是一件坏事。
null
是一个哨兵值时,编译器可以告诉我每次需要检查它或插入自己的检查并抛出异常。使用空对象模式,我只需要猜测在哪里需要放置检查,而历史表明程序员不擅长放置此类检查。 - GabeNull不是问题,问题在于语言允许你编写读取可能为空的值的代码。
如果语言要求任何指针访问都必须首先进行检查或转换为非空类型,那么99%与Null相关的错误将会消失。例如,在C++中。
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
}
}
不幸的是,这无法应用于大多数语言,因为它会破坏很多代码,但对于新语言来说是相当合理的。
我们会创建各种奇怪的结构来传达对象“无效”或“不存在”的消息,正如其他答案中所看到的。这是null
可以很好地传达的信息。
null
或使用Null Object模式没有太大区别。个人而言,我会编写一些C#预处理器,使我可以使用null
。然后这将映射到一些dynamic
对象,每当在其上调用方法时都会抛出NullReferenceException
。
回到1965年,空引用可能看起来像是一个错误。但现在,有各种代码分析工具警告我们空引用,我们不必太担心。从编程角度来看,null
是一个非常有价值的关键字。
实际上,在任何允许指针或对象引用的强大编程语言中,都会出现代码能够访问未经任何初始化代码运行的指针的情况。可能可以保证这些指针将被初始化为某些静态值,但这似乎并不是非常有用的。如果机器有一种通用的方法来捕获对未初始化变量(无论是指针还是其他东西)的访问,那比特殊处理空指针更好,否则我看到的最大的与空值相关的错误发生在允许使用空指针进行算术运算的实现中。将5添加到(char*)0不应该产生一个字符指针以寻址5;它应该触发一个错误(如果创建指向绝对地址的指针是适当的,应该有其他手段来完成它)。
如果没有NULL,我们该怎么办?发明它!:-) 如果您正在寻找一种表示实际上不是指针的内部指针值,那么即使不是火箭科学家也可以使用0。
double x;
你如何判断x
是否被初始化了?请翻译以上内容。 - KLee1double
在大多数语言中都是非空类型。你需要以相同的方式处理对象不为null以及决定double
是否已初始化。 - KLee1int
比double
更好作为例子。通常的 IEEE 浮点数实现中,double
提供了NaN
值,它们往往可以作为空值的合理替代。 - bcatNaN
不是null
... 它意味着你有一个值,但在当前上下文中没有意义;null
意味着你真的什么都没有。 - robert