[控场AI]
· 6 分钟阅读· 3,454 字

Tony Hoare与他的"十亿美元错误":空引用如何影响至今

Tony Hoare与他的"十亿美元错误":空引用如何影响至今

Tony Hoare一生既缔造了快速排序与CSP,也留下了"十亿美元错误"空引用,影响编程世界逾半世纪。

Tony Hoare是计算机科学史上最具双重性的人物之一:他在1960年代于纸笔上发明了至今仍是主流排序算法的快速排序(O(n log n)、内存占用极低),并提出了影响Go、Elixir等现代语言并发模型的CSP理论;但他也在1965年引入了空引用这一"十亿美元错误",导致数十年间无数系统崩溃,2025年Google Cloud长达七个半小时的全球宕机正是其最新注脚。Hoare本人在2009年公开忏悔,坦言这不过是因为"太容易实现了"。他留下的软件设计名言——"让它简单到明显没有缺陷,远比让它复杂到没有明显缺陷困难得多"——至今仍是工程哲学的精髓。今年3月,这位计算机科学的塑造者辞世,但他的算法、教训与思想将继续延续。

在众多传奇计算机科学家中,Tony Hoare(东尼·霍尔)占据着一个独特的位置。他既缔造了改变编程史的高效算法,也亲手埋下了困扰程序员半个多世纪的隐患——空引用(null reference)。用他自己的话说,这是一个"十亿美元的错误"。

一个人,两副面孔

Tony Hoare的职业生涯堪称矛盾体。一方面,他发明了快速排序(Quicksort)、QuickSelect等至今仍在广泛使用的高效算法,为Pascal、Go等语言奠定了理论基础,一生获得超过21项荣誉,包括被视为计算机科学最高奖项的图灵奖,并因对教育和计算机科学的贡献被授予爵位。

另一方面,据这位YouTube创作者半开玩笑的统计,企业级Java中约70%的已记录错误,可以追溯到他在1965年做出的一个决定——引入空引用。近年最严重的宕机事故之一,正是由一次空指针异常引发。

我上的是蒙大拿州立大学

1934年1月11日,Hoare出生于今天的斯里兰卡,接受了英国最顶尖的教育,先后就读于牛津的Dragon School、坎特伯雷的King School,1952年进入牛津默顿学院。毕业后,作为皇家海军义务役的一部分,他花了18个月学习俄语,并在莫斯科国立大学度过了一段时间。

快速排序:在没有计算机的年代诞生

最令人惊叹的是,Hoare发明快速排序时,手边既没有计算机,也没有支持递归的编程语言。他完全是在纸笔上完成了这一构想——用视频作者的话说,"就像他脑子里装了个ChatGPT"。

快速排序的原理其实并不复杂:随机选取一个基准元素(pivot),把比它小的放一边,比它大的放另一边,然后对两侧分别递归执行同样的操作。这样每一轮都能确定一个元素的最终位置,直到全部有序。

当年内存极其昂贵

快速排序之所以伟大,有两个关键原因。其一是运行时间:平均复杂度为 n log n,随着数组增大,增长曲线相当理想。其二是内存占用极低——这在1960年代至关重要。当时一千字节的磁芯内存售价高达5000美元(旧美元),折合今天约5万美元一KB。在这种成本背景下,一个省内存的算法价值连城。

有趣的是,Hoare很长时间里都没机会实现它。直到1960年加入Elliott Brothers公司,他的第一个任务是实现希尔排序(Shellsort)。他自信地告诉老板Pat自己能做得更好,两人为此打赌六便士。最终Hoare在白板上画出方框和箭头,Pat照着实现后承认自己输了——这大概是历史上回报率最高的六便士之一。

希尔排序(Shellsort)由Donald Shell于1959年提出,是插入排序的改进版本,通过比较相距一定间隔的元素来工作,逐步缩小间隔直至为1。它的平均复杂度约为O(n^1.5),虽然优于简单的O(n²)排序,但仍不如快速排序的O(n log n)。Hoare在承接希尔排序实现任务时,正是看到了这一改进空间。值得一提的是,快速排序在最坏情况下(如数组已经有序且每次选到最大/最小元素作为基准)复杂度会退化到O(n²),这也是后续工程实践中引入随机化选取基准(randomized pivot)的原因——通过随机性规避极端情况,使平均性能的理论保证在实际中更可靠。

"十亿美元的错误"

在Elliott Brothers期间,Hoare还说服公司采用ALGOL 60而非自研语言,取得了商业成功。此后他与Niklaus Wirth合作开发ALGOL W——正是这门语言,让空引用走进了我们的生活。

在2009年QCon演讲中,Hoare亲口忏悔:

"我称之为我的十亿美元错误。那是1965年空引用的发明。当时我正在为一门面向对象语言ALGOL W设计第一个完整的引用类型系统。我的目标是确保所有引用的使用都绝对安全……但我无法抵挡加入空引用的诱惑,仅仅因为它太容易实现了。这导致了无数错误、漏洞和系统崩溃,在过去40年里可能造成了约十亿美元的损失。"

仅仅因为它太容易实现了

这个"诱惑"的代价直到今天仍在显现。视频提到,2025年6月的一次Google Cloud大规模宕机,根源就是没有检查空指针——一个简单的if判断被跳过,导致API和认证服务全面瘫痪,全球范围内多项服务受影响长达约七个半小时,Spotify等平台无一幸免,损失以百万美元计。

一个讽刺至极的事实是:Hoare本想打造一门"绝对安全"的类型系统,却反而制造了至今仍在修复的最大隐患之一。不过正如作者所言,即便Hoare不发明它,也会有别人发明——这就像今天的AI竞赛一样,某些技术的出现几乎是历史的必然。

空引用(null reference)本质上是一个特殊的指针值,表示"此处没有对象"。问题在于,程序在试图通过空引用访问对象的属性或方法时,运行时会抛出空指针异常(NullPointerException),而这种检查往往被开发者遗漏。现代语言已开始从类型系统层面修复这一设计:Kotlin、Swift、Rust等语言引入了"可空类型"(Nullable Types)与"非空类型"的区分,强制开发者在编译阶段处理空值情况,而非等到运行时崩溃。Rust更进一步,用Option<T>枚举完全替代了空引用概念。Hoare本人事后反思,若当时类型系统强制要求每次使用引用前都检查其是否为空,这个错误本可避免——代价只是稍微麻烦一些的语法。

CSP:并发编程的思想源头

Hoare对计算机科学的另一大贡献是通信顺序进程(Communicating Sequential Processes,CSP)。视频用一段Go代码作了直观演示:

进程一创建一个channel并阻塞等待;进程二执行工作后通过channel发送消息(比如"Hello World");只有当进程二完成通信时,进程一才收到消息并继续执行。

Go示例中的channel通信

CSP的核心思想是:通信是一次同步握手。进程一必须等待进程二发出通信,双方彼此等待——除非引入缓冲区(buffer),才能让发送方不必立即阻塞。进程是行为的独立单元,彼此之间不共享变量。

如果你写过Elixir,会觉得这套模型格外熟悉,因为它正是actor模式的思想来源。Go的goroutine与channel机制,本质上也是CSP理念的现代实践。

CSP(通信顺序进程)由Hoare于1978年在论文中正式提出,其核心主张是:并发系统的复杂性应通过"通信"而非"共享内存"来管理。传统多线程编程依赖共享变量加锁(mutex/semaphore)来协调并发,极易引发死锁、竞态条件等难以调试的问题。CSP则让每个进程保持独立状态,只通过发送/接收消息来交互,将并发逻辑转化为更易推理的通信序列。Actor模型与CSP思想相近但有所区别:Actor中每个实体拥有自己的邮箱,发送是异步的;而纯CSP的channel通信默认是同步阻塞的握手。Go的channel同时支持有缓冲与无缓冲两种模式,恰好覆盖了这两种语义,使其成为CSP理念在工业级语言中最广泛的实现之一。

一位能"看穿拐角"的思想家

Hoare的贡献远不止这些——QuickSelect、统计算法、ALGOL编译器的实践工作、程序设计的统一理论、哲学家就餐问题(Dining Philosophers Problem)等,都留下了他的印记。

他还留下了一些广为流传的名言。谈到ALGOL 60时,他说:"这门语言如此超前,以至于它不仅是对前辈的改进,还几乎优于它所有的后继者。"

而最发人深省的一句,是关于软件设计的:

"构建软件设计有两种方式。一种是让它简单到明显没有缺陷;另一种是让它复杂到没有明显的缺陷。第一种方式要困难得多。"

Hoare于今年3月去世。视频作者感慨,不知道这位能"看穿拐角"的老人,如何看待当下AI生成的海量代码。无论如何,从快速排序到CSP,从空引用的教训到简洁设计的哲学,Tony Hoare的影响还将延续很久很久。

分享:

相关推荐