← 所有文章

工程哲学:Thompson 与 Ritchie,把一件事做好

Ken Thompson(坐着)与 Dennis Ritchie 在贝尔实验室的一台 PDP-11 前

核心要点

  • 小巧而锋利的工具。 每个程序只把一件事做好,并拒绝去做第二件;遇到新需求,就造一个新工具,而不是再加一个参数。
  • 组合优于功能。 力量来自通过通用文本接口——管道——把程序连接起来,于是能力诞生在接缝处,而非任何单个程序内部。”把一件事做好”这句经典表述出自 Doug McIlroy,而非 Thompson 或 Ritchie。
  • 靠 C 实现可移植。 用 C 重写 Unix,使它只需移植一个编译器就能迁移到新硬件,而无需重写整个系统——这正是 Unix 得以无处不在的主要原因。
  • 不信任任何非你亲手所造之物。 Thompson 的《Reflections on Trusting Trust》表明,后门可以潜伏在二进制文件中,却在你能审查的任何源代码里不留痕迹——这是供应链安全的奠基洞见。

这条原则

“编写只做一件事并把它做好的程序。编写能够协同工作的程序。编写能处理文本流的程序,因为文本流是一种通用接口。”——Doug McIlroy,对 Unix 哲学的概括1

这句话既不是 Thompson 的,也不是 Ritchie 的。它出自 Doug McIlroy——他们在贝尔实验室的同事,也是 Unix 管道的发明者——写在 1978 年《Bell System Technical Journal》专刊的前言里。1但这是对 Thompson 与 Ritchie 实际构建之物最干净利落的表述;而之所以选它开篇、而不引用两人中任何一位自己的话,本身就道出了要点:Unix 哲学从来不是某位天才独自写下的宣言。它是一种文化,而这种文化本身就是作品。

这条原则有三步,三者密不可分。让每个工具小巧而锋利——只干一件事,把它干好,毫无去干第二件的野心。让工具可组合——一个的输出就是下一个的输入,于是能力来自组合,而非来自任何程序变得庞大。再让它们之间的接口通用化——纯文本,这样任何工具都能与任何其他工具相连,彼此甚至无需知道对方存在。力量栖身于接缝,而非部件。一个对行排序的程序对网站日志一无所知;一个统计重复项的程序对 HTTP 状态码一无所知;一根管道把它们连起来,便回答了任何一方都不是为之而生的问题。

这与催生大多数软件的本能恰恰相反。自然的拉力是让一个程序做得更多——加一个参数,加一种模式,把下一个需求塞进你手头已有的东西里。McIlroy 的第一条规则点破了这种拉力并拒绝了它:”要完成一件新工作,就从头另建,而不要靠添加新’功能’来把旧程序搞复杂。”1Thompson 与 Ritchie 的整个系统,正是这样一个论证:简洁是其他一切的前提,而组合——而非功能——才是小东西变强大的途径。

背景

Unix 诞生于一次失败。整个 20 世纪 60 年代中期,贝尔实验室与 MIT、通用电气合作开发 Multics——一个雄心极其宏大、意图几乎无所不能的分时操作系统。到 1969 年,它已变得如此复杂、如此延期,以致贝尔实验室退出了项目;曾参与其中的研究者——Ken Thompson、Dennis Ritchie、Doug McIlroy、Joe Ossanna——满怀沮丧,既失去了一个舒适的交互式计算环境,也获得了一种强烈的体悟:系统不该这样建。2

Dennis Ritchie,C 语言的创造者

Ken Thompson 于 1943 年 2 月 4 日生于新奥尔良,在 UC Berkeley 取得电气工程与计算机科学的学士与硕士学位后,于 1966 年加入贝尔实验室。3Dennis Ritchie 于 1941 年 9 月 9 日生于纽约州布朗克斯维尔,1967 年加入计算科学研究中心。41969 年,Thompson 找到一台被弃用的 PDP-7——一台小型、彼时已过时的小型计算机——开始在上面构建一个极小的操作系统,部分是为了运行他写的一款名为《Space Travel》的游戏。Thompson 后来直白地讲述了分工:”UNIX 头两三个版本中的第一个,完全是我一个人做的。而 Dennis 成了布道者。”3第一个版本是用 PDP-7 汇编写的。

名字源于一个玩笑。Multics 意为”多路复用的”(Multiplexed),而新系统刻意做得很小,于是这群人称它为”UNICS”——Uniplexed Information and Computing Service(单路信息与计算服务)——这是对 Multics 的一句双关调侃。拼写后来漂移成了”Unix”;通常被认为是命名者的 Brian Kernighan 后来表示,谁也记不太清最终形态是怎么定下来的。2这种小,既非偶然,也不是一种有待克服的局限。它就是设计本身。

工作

Unix 与文件系统

Unix 做对的第一件事,是为计算机的资源究竟是什么建立了一个模型。早先的系统把文件、设备和通信通道当作不同种类的东西,各有各的专用接口。Unix 把它们合而为一。它呈现出一个单一的层级式文件系统——一棵目录之树——继而把几乎一切都当作其中的文件来对待:不只是文档,还有设备,以及进程间通信通道。2一个能读写文件的程序,无需任何改动,就能从终端、磁带机或另一个程序读取数据,因为在这个程序看来,它们全都只是文件。

这便是通用接口的最初形态。”一切皆文件”意味着,一个小工具不必为每种输入都备一个不同的版本;它只需一个。组合的成本几乎降到零,因为每个组件早已说着同一种语言。层级式文件系统,以及统一的 open/read/write/close 接口,正是后续整套哲学之所以可能成立的根基:你无法组合那些各自要求专属接头的工具。

C 与可移植性

第二项突破,是让系统可移植,而这需要一门语言。Ritchie 在 1969 至 1973 年间创造了 C,它由 Thompson 的 B 语言演化而来,而 B 又源自 BCPL。4B 是无类型且解释执行的;C 加入了数据类型与结构,并编译为高效的机器码——既贴近硬件足以用来写操作系统,又足够抽象以便在机器之间迁移。

决定性的时刻出现在 1973 年前后,Version 4 Unix 用 C 重写了。2在此之前,操作系统就等于汇编语言,被永久焊死在它所写的那一台机器上。用一门高级语言重写 Unix 近乎离经叛道——按照当时的常识,系统代码必须是手工调优的汇编,否则就会太慢。Thompson 与 Ritchie 还是这么干了,回报便是可移植性:Unix 如今只需移植一个编译器、而非重写整个系统,就能迁移到新硬件。正是这一个决定,造就了 Unix 渗透进计算世界每一个角落的大部分原因,而它同时代的系统却仍被螺栓死死拧在各自最初的铁皮机器上。1978 年,Ritchie 与 Kernighan 出版了《The C Programming Language》——即”K&R”——这本简练而精确的书教会了一代人写 C,至今仍是技术文字的典范。5Thompson 与 Ritchie 则在 1974 年 7 月的《Communications of the ACM》论文《The UNIX Time-Sharing System》中,把系统本身向更广阔的世界公之于众。6

管道与组合模型

Ken Thompson,Unix 的共同创造者

如果说文件系统让工具说起了同一种语言,那么管道让它们彼此交谈。这个想法出自 Doug McIlroy:多年来他一直在推动一种把程序首尾相连的方式,让一个的输出像一段段花园水管那样直接流入下一个。Thompson 在 1973 年实现了它——据说是在一个晚上之内——为 shell 添加了 pipe() 系统调用和 | 操作符。7McIlroy 这样描述接下来发生的事:”第二天就上演了一场难忘的单行命令狂欢,每个人都投身于’接水管’的兴奋之中。”7

那场单行命令的狂欢,就是这套哲学的实战。没人需要写新程序;他们发现,自己手头已有的那些小程序,可以串联起来回答任何一个都未曾被设计去回答的问题。一条像 grepcutsortuniq -csort -rnhead 的流水线,由六个阶段构成,每个阶段都对其他阶段一无所知,每个阶段都只做一件事,经由一道文本流从左到右组合而成。能力是涌现出来的。它栖身于 |,而非任何单条命令之中。

这是整个系统中最深刻的想法。大多数软件靠堆积生长:一个程序不断吸纳每一个新需求,直到变成一个只有它的作者才看得懂的庞然大物。Unix 靠组合生长:系统始终是一套小部件的工具箱,新能力来自对旧部件的新排列。文本流就是那个让排列变得免费的通用接口。这正是为什么 Unix shell 历经五十年,仍是现存表达力最强的编程环境之一——不是尽管它由微小的单一用途程序组成,而恰恰因为如此。

信任的信任,与日后的遗产

1983 年,Thompson 与 Ritchie 共同获得 ACM 图灵奖。颁奖词写道,授奖是”表彰他们在通用操作系统理论上的发展,尤其是 UNIX 操作系统的实现”;而委员会的声明恰好点出了那条原则:”UNIX 系统的成功,源于它对少数几个关键思想富有品味的取舍,以及对它们的优雅实现。”8对少数几个关键思想富有品味的取舍——这个奖,实际上是颁给克制的。

Thompson 自己的图灵奖演讲,1984 年以《Reflections on Trusting Trust》为题发表,把同样的极简主义严谨转向了安全。他演示了如何教会一个编译器,往 login 程序里插入一个后门,并把这套”插入后门”的逻辑插进它日后编译的任何编译器中——于是恶意代码只活在二进制文件里,在你能审查的任何源代码中都不留痕迹。他的结论是计算领域被引用最多的句子之一:”你无法信任并非完全由你自己创造的代码……再多的源码层面验证或审查,都无法保护你免受使用不可信代码之害。”9这是供应链安全思想的奠基文本——它认识到,你所信任的并非源代码,而是生成那份二进制文件的整条工具链。

两人日后的生涯延续了同样的直觉。Thompson 始终不肯停歇:在贝尔实验室,他构建了《Plan 9》操作系统(把”一切皆文件”进一步推向网络之上)、《ed》编辑器,以及正则表达式与《grep》的实用理论。1992 年,他与 Rob Pike 设计了 UTF-8——这种如今承载着世界上几乎所有文本的可变宽度文本编码——据说就在一个晚上,画在了一张餐垫纸上。32007 年,在 Google,Thompson 与 Robert Griesemer、Rob Pike 共同设计了 Go 编程语言,并于 2009 年公开发布:一门刻意做得小巧、编译迅速、可组合的语言,读来像是对 Unix 价值观的一次自觉回归。10Ritchie 于 2011 年 10 月 12 日离世,留下的印记更安静却更无处不在;正如历史学家 Paul Ceruzzi 所言:”如果你拿一台显微镜往计算机里看,你会发现他的作品遍布其内部各处。”4

方法

这套方法在四十年间、在两个几乎从不需要解释它的人身上,始终如一。

构建小巧而锋利的工具。 每个程序只干一份活。这种纪律在于拒绝:当新需求出现,答案是一个新工具,而不是往旧工具上又拧一个参数。”从头另建,而不要把旧程序搞复杂。”1

组合,而非堆积。 能力理应从组合中涌现。当一个系统是一套小部件的工具箱、靠重新排列而非靠某个部件膨胀成庞然大物来生长时,它就始终可被理解。

标准化接口,而非工具。 那个起统一作用的招法——“一切皆文件”,程序之间的纯文本流——就是让连接通用化,好让组件彼此保持无知。廉价的接缝,正是小部件之所以强大的原因。

富有品味地选取少数几个关键思想。 图灵委员会那句话不是恭维;它就是方法本身。Unix 的著名之处在于它略去了什么。靠 C 实现的可移植性、文件抽象、管道——寥寥几个决定,每一个都承载着巨大分量,而冗余之物被刻意摒弃。

不信任你无法审查之物。 《Trusting Trust》是这套方法向内的指向:简洁与可读不仅是审美上的美德,更是信任唯一真实的根基,因为复杂正是那些你看不见的东西藏身之处。

影响链

谁塑造了他们

Multics,以反面教材的方式。 最具塑造力的单一影响,是一个他们参与构建、随后转身离开的系统。Multics 以代价最为高昂的方式,教会了 Thompson 与 Ritchie 过度雄心的代价是什么——而 Unix 在很大程度上,正是对它一种有纪律的反动。(塑造性影响)

Doug McIlroy。 作为计算科学研究中心的负责人,McIlroy 发明了管道,用文字阐明了”把一件事做好”的哲学,并扮演着这群人不知疲倦的品味批评者。组合模型既是任何人的,也同样是他的;而对于这套哲学的经典表述,他才是正确的署名者。(直接影响)

贝尔实验室的研究文化。 一小群同侪,被赋予自由与好机器,在彼此之间为优雅而优化,而非为某张产品路线图而优化。Unix 不是由管理层规定出来的;它从一种推崇富有品味、嘲讽臃肿的文化中逐渐积淀而成。(塑造性影响)

他们塑造了谁

每一个 Unix 的后裔。 Linux、各种 BSD 和 macOS 都是直系传人;Thompson 与 Ritchie 在一台被弃用的 PDP-7 上写下的系统,如今运行着地球上大多数的服务器、手机和嵌入式设备。

每一个 shell 与命令行。 管道与组合模型,是几乎每一位程序员和系统管理员日常工作的环境,历经五十年——其精髓未曾改变,因为那精髓本就是对的。

C 及其子嗣。 C 成了系统编程的通用语,其语法与语义在 C++、Java、Go、Rust 以及今天所写的大部分东西中回响。K&R 至今仍是”如何为一门语言撰写文档”的范本。

Unix 哲学本身。 “把一件事做好”超越了 Unix,成为一条通用的设计原则——用于微服务,用于命令行工具,用于免构建、小部件式的 Web 路径,也用于如何让 AI 系统保持足够小巧、以便始终安全可读

贯穿始终的主线

Thompson 与 Ritchie,是贯穿本系列那同一信念在系统层面的表达。Edsger Dijkstra 论证简洁是可靠的前提;Unix 就是那个论证被编译并交付出来的成果——一个由少数几个关键思想构成的内核,小到足以被信任。Linus Torvalds 的”好品味”——那次让特例消失的重写——是”把一件事做好”落在单个函数内部、而非横跨一条流水线;而 Torvalds 正是直接在这两人所奠的基石之上,构建了 Linux 与 Git。John Carmack 那快速、简单的内核,是同一种减法,只不过瞄准的是硬件的天花板。而松本行弘(Yukihiro Matsumoto)为人的幸福而设计 Ruby,则是把 Unix 的直觉——可组合、富表达力的小部件——向上托举进了一门高级语言。这条主线可以浓缩成一句话:力量应当来自通过一个通用接口去组合小巧、可理解的部件,而非来自任何单个部件变得庞大。(系列桥接)

我从中汲取了什么

我始终记取的教训是:接口即架构。如今每当我设计任何东西——一个智能体、一个工具循环、一条脚本流水线——诱惑总是让某个组件更聪明,把下一个需求塞进我手头已有的东西里,直到它变成一个只有我自己看得懂的庞然大物。Unix 的招法恰恰相反:让每个部件小到我能完全理解,再把智慧放进部件如何相连之中。在愚笨部件之间架一道干净、通用的接缝,每一次都胜过造一个没有干净接缝的聪明部件。这与质量是唯一的变量是同一条标准——问题从来不是”这一个程序能不能做得更多?”,而是”这个系统是否仍由我能推理的部件组成?”

在我如今所构建的世界里——用工具与文本接线而成的 AI 智能体——Thompson 与 Ritchie 的设计,以一种它在五十多岁高龄本不该拥有的程度,显得格外切题。一个智能体是一个小程序;一个工具是一个小程序;让它们强大的,是它们之间一道干净、通用的接口,而今天这接口大多是结构化文本。那就是管道,被重新造了出来。而《Trusting Trust》是我最为认真对待的警告:当一个系统生成代码的速度快过任何人能阅读的速度时,信任唯一持久的根基,就是让部件保持小巧、让接缝保持可读,因为复杂正是那些你看不见的东西去藏身的地方。这份信念——品味是一套技术系统,小巧、可组合、可审查的部件不是锦上添花,而是全部要义——从 1969 年的一台 PDP-7,一路笔直延伸到 2026 年的一个智能体框架。

常见问题

什么是 Unix 哲学?

Unix 哲学是一套建立在小巧、单一用途工具之上的设计取向,这些工具通过一个通用接口相互组合。它的经典表述由 Doug McIlroy 于 1978 年写下:”编写只做一件事并把它做好的程序。编写能够协同工作的程序。编写能处理文本流的程序,因为文本流是一种通用接口。”其首要原则是构建一个新工具、而不是往现有工具上添加功能;系统的力量来自把小部件组合起来——经由管道与纯文本——而非来自任何单个程序变得庞大。1

Ken Thompson 说过”把一件事做好”吗?

没有。”把一件事做好”这句格言,以及更宽泛的 Unix 哲学,是由贝尔实验室计算科学研究中心负责人 Doug McIlroy 在 1978 年《Bell System Technical Journal》Unix 专刊的前言中以文字阐明的。1Thompson 与 Ritchie 构建了体现这套哲学的系统,而 McIlroy——也是 Unix 管道的发明者——是它最清晰的阐述者,也是这群人常驻的品味批评者。把这句格言归到 Thompson 名下,是一种常见却错误的省事说法。

Thompson 与 Ritchie 究竟创造了什么?

Ken Thompson 自 1969 年起在一台 PDP-7 上构建了 Unix 操作系统最初的几个版本,日后又创造了 B 语言、《ed》编辑器、实用的正则表达式搜索(《grep》)、Plan 9 系统、UTF-8(与 Rob Pike 合作),并在 Google 共同设计了 Go。3Dennis Ritchie 创造了 C 编程语言(1969—1973),用它在 1973 年重写 Unix 使之可移植,并于 1978 年与 Brian Kernighan 合著了《The C Programming Language》。45两人因 Unix 共同获得 1983 年 ACM 图灵奖。8

《Reflections on Trusting Trust》是什么?

它是 Ken Thompson 1984 年的 ACM 图灵奖演讲,发表于《Communications of the ACM》。他展示了如何让一个编译器往程序里插入一个隐藏后门,并在它每次编译自身的新版本时复制该后门——于是恶意代码只存在于二进制文件中,在你能读到的任何源代码里都不留痕迹。他的结论是:”你无法信任并非完全由你自己创造的代码……再多的源码层面验证或审查,都无法保护你免受使用不可信代码之害。”它是软件供应链安全的一篇奠基性文本。9


来源


  1. M. Douglas McIlroy,为“UNIX Time-Sharing System,”The Bell System Technical Journal》,第 57 卷,第 6 期,第 2 部分(1978 年 7—8 月)所作前言。其四点概括(”让每个程序只做一件事并把它做好……从头另建,而不要靠添加新’功能’来把旧程序搞复杂”)与精炼的后期形式:”编写只做一件事并把它做好的程序。编写能够协同工作的程序。编写能处理文本流的程序,因为文本流是一种通用接口。”另见“Unix philosophy,” Wikipedia。 

  2. “Unix,” Wikipedia。贝尔实验室于 1969 年放弃 Multics;PDP-7 起源;”Unics”/”Unix” 命名(Brian Kernighan);Version 4 Unix 于 1973 年为可移植性用 C 重写;层级式文件系统,以及把设备和 IPC 当作文件来处理(”一切皆文件”);管道;1974 年 CACM 论文。 

  3. “Kenneth Lane Thompson,” Wikipedia。1943 年 2 月 4 日生于新奥尔良;UC Berkeley 学士/硕士;贝尔实验室(1966);Unix 最初的几个版本(”UNIX 头两三个版本中的第一个,完全是我一个人做的。而 Dennis 成了布道者。”);B 语言;《ed》、正则表达式、《grep》;Plan 9;与 Rob Pike 合作的 UTF-8(1992);Google 的 Go(2007 起)。 

  4. “Dennis Ritchie,” Wikipedia。1941 年 9 月 9 日生于纽约州布朗克斯维尔;2011 年 10 月 12 日逝世;贝尔实验室计算科学研究中心(1967);创造了 C(由 B 与 BCPL 演化而来)并用它重写 Unix 以实现可移植性;Paul Ceruzzi 引语(”如果你拿一台显微镜往计算机里看,你会发现他的作品遍布其内部各处”)。 

  5. “The C Programming Language,” Wikipedia。Brian Kernighan 与 Dennis Ritchie 著,1978 年 2 月 22 日首次出版(Prentice Hall);人称”K&R”;第一本广泛可得的 C 语言著作。 

  6. D. M. Ritchie 与 K. Thompson,“The UNIX Time-Sharing System,”Communications of the ACM》,第 17 卷,第 7 期(1974 年 7 月),第 365—375 页。这篇论文把 Unix 向更广阔的计算社区公之于众。 

  7. “Pipeline (Unix),” Wikipedia。管道概念由 Doug McIlroy 构想;由 Ken Thompson 于 1973 年实现(pipe() 系统调用与 | 操作符)。McIlroy:”第二天就上演了一场难忘的单行命令狂欢,每个人都投身于’接水管’的兴奋之中。” 

  8. “Dennis M. Ritchie – A.M. Turing Award Laureate,” ACM(镜像)。1983 年颁奖词:”表彰他们在通用操作系统理论上的发展,尤其是 UNIX 操作系统的实现”;委员会声明:”UNIX 系统的成功,源于它对少数几个关键思想富有品味的取舍,以及对它们的优雅实现。” 

  9. Ken Thompson,“Reflections on Trusting Trust,” 1984 年 ACM 图灵奖演讲,《Communications of the ACM》,第 27 卷,第 8 期(1984 年 8 月),第 761—763 页。其 MORAL 一节:”你无法信任并非完全由你自己创造的代码。(尤其是那些雇用像我这样的人的公司所产出的代码。)再多的源码层面验证或审查,都无法保护你免受使用不可信代码之害。” 

  10. “Go (programming language),” Wikipedia。”它于 2007 年在 Google 由 Robert Griesemer、Rob Pike 和 Ken Thompson 设计,并于 2009 年 11 月公开发布。” 

相关文章

工程哲学:John Carmack,把性能当作一种道德手艺

John Carmack 把性能视为一个道德问题。剥离到那个快速而简单的内核,把问题理解到根基,发布它,并公开你的全部过程。

3 分钟阅读

工程哲学:Leslie Lamport,先思考再写代码

Leslie Lamport 让分布式系统成为一门科学:时间不是全局的,真正实在的是因果,先写明设计规约,再动手写代码。

1 分钟阅读

The Shader Gallery That Lied: Debugging 216 WebGL Presets

A user said the shader playground looked broken. Pixel-readback testing found 30 dead presets, 11 that never compiled, a…

11 分钟阅读