偶然看到一篇文章:《“NULL”:计算机科学中的最严重错误,造成十亿美元损失》
主要内容是说明 NULL 是计算机科学中最糟糕的错误,并引用了图灵奖得主 Tony Hoare 的话
“我把 Null 引用称为自己的十亿美元错误。它的发明是在 1965 年,那时我用一个面向对象语言 ( ALGOL W ) 设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了 Null 引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。近年来,大家开始使用各种程序分析程序,比如微软的 PREfix 和 PREfast 来检查引用,如果存在为非 Null 的风险时就提出警告。更新的程序设计语言比如 Spec# 已经引入了非 Null 引用的声明。这正是我在 1965 年拒绝的解决方案。” 《Null References: The Billion Dollar Mistake》托尼·霍尔(Tony Hoare)
文章通过这几个观点逐步进行了阐述:
- 颠覆类型
- 是凌乱的
- 是一个特例
- 使 API 变得糟糕
- 使错误的语言决策更加恶化
- 难以调试
- 是不可组合的
其中”API 变得糟糕“以 Ruby 举了个例子,
假设我们创建一个 Ruby 类充当一个键值存储。这可能是一个缓存、一个用于键值数据库的接口等等。我们将会创建简单通用的 API:
class Store
# associate key with value
def set(key, value)
...
end
# get value associated with key, or return nil if there is no sch key
def get(key)
...
end
end
我们可以想象在很多语言中类似的类(Python、JavaScript、Java、C# 等)。 现在假设我们的程序有一个慢的或者占用大量资源的方法,来找到某个人的电话号码——可能通过连通一个网络服务。
为了提高性能,我们将会使用本地存储作为缓存,将一个人名映射到他的电话号码上。
store = Store.new()
store.set('Bob', '801-555-5555')
store.get('Bob') # returns '801-555-5555', which is Bob’s number
store.get('Alice') # returns nil, since it does not have Alice
然而,一些人没有电话号码(即他们的电话号码是 nil)。我们仍然会缓存那些信息,所以我们不需要在后面重新填充那些信息。
store = Store.new()
store.set('Ted', nil) # Ted has no phone number
store.get('Ted') # returns nil, since Ted does not have a phone number
但是现在意味着我们的结果模棱两可!它可能表示:
一种情形要求昂贵的重新计算,另一种需要即时的答复。但是我们的代码不够精密来区分这两种情况。 在实际的代码中,像这样的情况经常会以复杂且不易察觉的方式出现。因此,简单通用的 API 可以马上变成特例,迷惑了 null 凌乱行为的来源。 用一个 contains() 方法来修补 Store 类可能会有帮助。但是这引入重复的查找,导致降低性能和竞争条件。
大家如何看待这个问题