跳到主要内容

小、专、能组合 — UNIX 哲学

这一章讲四件事: 「一个软件只做一件事」为什么是起点; 过滤器与文本流怎么让软件像水管一样组合;沉默与最小意外为什么是组合的前提; 三系统论怎么回答「能不能一步到位」。 主走查:两个单责小命令拼成一条流水线,数据在每一站长什么样。

1. 为什么值得为 1969 年的操作系统单开一章

UNIX 是 1969 年诞生的操作系统(管理整台计算机的硬件、以及跑在其上的一切程序的底层系统)。 半个多世纪过去,它的后裔仍跑在全球的服务器、手机和开发机上。 原书把这一点当成了论证本身:从信息技术的更新速度看, 「这确实是一个傲人的成绩」——而它成功的理由,是早期程序员做对了一组设计判断; 这些判断沉淀成一套思想,「虽然 UNIX 思想源于 UNIX 文化,但它在任何软件开发领域都有借鉴意义」1

本书把 UNIX 的遗产分成两层,本拆解按因果链重组2:

  • UNIX 思想(17 条原则):怎么写单个程序——模块化、清晰、简单…… 大部分与前面各章的原则同源,本章不再重复,只在§5 做一张对照表;
  • UNIX 哲学(9 条定理):怎么组织软件群——小、专、组合。 这是 UNIX 真正独特、且今天仍在主流的东西,本章的主菜。

2. 起点:一个软件只做好一件事

这一节回答:整条链的第一环——「专」。

工作唯一(Make each program do one thing well)的主张: 每个软件只负责做好一项工作;做完就退场,把数据交给下一个软件3。 书里给了一个最小判定例:一个「显示目录内容」的命令,不该顺手带「美化输出格式」的功能—— 想美化,另写一个命令,然后组合使用4

这个要求的深层逻辑有三层5:

  1. 单责暴露理解程度:「写不出这种只做好一项工作的软件,就证明我们对问题的理解还不够透彻」—— 功能收不拢,通常是因为问题还没想清;
  2. 单责保持纯粹:多余的代码拖慢运行、抬复杂度、妨碍维护——第 03 章的账在这里再结一次;
  3. 单责才敢复用:没人敢复用一个大而全的软件(它带着你不需要的全部复杂度); 单责的小工具拿起来就用。

执行层的敌人很明确——多功能主义:功能一个个往上加,软件一点点偏离目标。 书里的自我盘问值得抄下来贴在屏幕旁:「这些代码是否真的有用,是否偏离了软件的本质?」6

3. 过滤器:软件之间的通用插座

这一节回答:各自独立的软件,靠什么接起来——答案是所有人都看得懂的格式:文本。

过滤器(filter)是 UNIX 对软件形态的最终抽象: 接收一条数据流(源源不断送来的数据),加工,再以流的形式输出**。软件的本质被重新定义为 「处理数据,而不是生成数据」——连图形界面软件都是过滤器: 把鼠标键盘事件当输入流,把屏幕更新当输出流7

光有形态还不够,还要有通用语言:数据用什么格式交流? UNIX 的答案是文本——文本是可移植性最强的格式(比程序本身更轻便、更能跨系统搬家), 人能直接打开看,工具能直接处理,不需要先转换格式8。 配套的三个标准通道9:标准输入(默认从键盘来)、标准输出(默认往屏幕去)、 标准错误(错误信息专用通道)——软件之间能连接(可连接性),全靠这三条通道口径统一

主走查:两个单责命令拼成一条流水线

拿书里「显示目录内容」的场景走完整条链(命令与输出为演示,机制与 UNIX 实际一致)10:

需求:当前目录下,按大小排序列出文件

传统做法(多功能主义):
一个大命令 ls-sorted-pretty,内嵌排序、格式化、统计……
下次要「按日期排」?改这个命令,或再写一个。

UNIX 做法(组合):
ls → 只负责「列出文件名」
│ 输出:notes.txt photo.jpg report.docx (纯文本流)
sort → 只负责「对文本行排序」
│ 输出:photo.jpg report.docx notes.txt
wc -l → 只负责「数行数」
输出:3

图说:竖线(管道)把上一个软件的标准输出,接到下一个软件的标准输入。
每一站都是过滤器:收文本 → 加工 → 出文本。三站各管一件事,
组合出了谁都没有单独实现的功能。

这条流水线里藏着 UNIX 哲学的全部要素:每一站单责(§2)、每一站都是过滤器传递的是文本(§3)、连接零成本(管道)。书里对这种协同给出了经济学表述: 无法协作的软件「通常发挥不出多少价值」;而能协作的软件, 「通过不同的组合方式,能够完成多种多样的任务」11

杠杆:组合的复利

组合的价值放大器是杠杆效应。书里借物理杠杆讲:个体的力有限, 把多个个体的力聚起来、加上杠杆,力就增大很多倍—— 放到软件上,杠杆的支点就是重复使用既有软件、把它们组合起来12。 最省力的路径被那句著名的格言钉住: 「好的程序员能写出好的代码,伟大的程序员能借来好的代码。」13

连接器也现成:shell 脚本(shell=命令行解释器;脚本=把一串命令写成文件一次执行)。 书里推荐它当胶水语言(连接软件与软件的语言)有两条硬理由14: 它是解释型语言(不用编译),天然跨平台可移植; 命令都是现成的——脚本本身就是最大的杠杆。 用编译型语言当胶水反而亏:编译产物绑平台、编译过程打断节奏,而速度优势几乎为零15

4. 组合的两个前提:沉默与最小意外

这一节回答:凭什么别人的软件敢接在你的软件后面——两条关于「输出」的纪律。

沉默原则(Rule of Silence):软件应当沉默寡言,只输出重要的信息16。 它在组合的语境下才显出真正的分量:你的输出是别人的输入, 「信息如果玉石混淆,就会影响到软件与其他软件的连接」17。 书里的对比很生动:列目录的命令,目录为空时,别的系统会显示「没有找到文件」, UNIX 什么都不显示——因为那句人话对下一个程序是噪音,对知道语境的人也是废话18。 连「程序正在启动」「正在关闭」这类话都被点名是废话:用户知道, 程序更不会拿它当输入19。 实操两条20:错误只走标准错误通道,错误之外多余的东西一样都不给; 调试信息放进冗余模式,默认关闭

最小意外原则(Rule of Least Surprise):接口的行为要符合使用者的预期—— 「需要用户学习的东西最少」的软件才用得住21。 具体到组合:计算器软件里「+」必须永远表示加法,因为全世界的计算器都这样22; 取本地值叫 get,取远程值就别也叫 get 了——换名 fetch,免得使用者「意外」23。 它还有一条反直觉的告诫:最糟的是「相似但稍有不同」—— 长得像旧软件,行为却偶尔不同,比完全不同更坑人24

这两条合起来是组合的信任基础:上游沉默(输出可信、无噪音), 下游守约(行为符合预期),陌生软件之间才敢直接对接—— 这就是第 05 章「把对方当黑箱」在软件群层面的样子。

5. UNIX 思想对照表:与前面各章的重逢

UNIX 思想的 17 条里,大半与前面各章的原则是同一件事的另一个名字—— 书在前言里说过,「许多原则虽然在观点和出发点方面有所不同,但关键部分是相通的, 所以最终会得出同一个结论」25。把重逢关系列出来,101 条里要背的东西立刻减半:

UNIX 思想说的是哪件旧事在哪章
模块化原则封装+精简接口:复杂度没有捷径,只能一步步精简04/05
清晰原则不应巧妙应清晰;第二次读不懂就处理02
组合原则本章 §3 过滤器08
分离原则策略与机制分离(X Window 只做机制)04
简单原则/简约原则代码自然变复杂的两个推手(自我表现欲、销售方的功能噱头);不写大代码03
透明性原则+公示性运行状态可见——「调试占用开发周期的大部分时间」,为调试的功能不是附属品06
健壮性原则健壮=透明+简单(结构看得懂才修得好)06/07
表达性原则信息放数据侧不放逻辑侧(换算表数组胜过 switch 语句)——数据比逻辑好懂03
修复原则修复失败响亮地停止07
经济原则程序员的时间比设备贵;买设备是间接给人投资10
生成原则写「生成代码的代码」;重复、形式固定的代码最适合03
优化原则先正确后快;「最有力的优化工具是删除键」10
多样性原则不存在唯一正确方法,宣称唯一正确「既狂妄又不成熟」10
可扩展性原则可插拔设计;数据格式自我描述(带版本号)04
最小意外/沉默原则本章 §408

没进对照表的一条——避开交互式用户接口——值得单独交代,因为它是组合的直接敌人: 交互式软件把用户「捆绑」在自己的界面里,从启动到结束不让数据出去26。 书里列的害处句句指向组合27:软件之间无法对话(为优化人的输入牺牲了机器的连接)、 等待人的输入成为机器瓶颈、菜单诱发多功能主义(「只能让自己变得越来越大」)。 解法是把控制权还给命令行:「和软件进行对话的应该是软件,而不是人」28。 它同时给出了人情味的补丁:给初学者留交互界面,给老用户留可脚本化的口子——两套并存29

6. 三系统论:为什么这条路走不快,但走得远

这一节回答:急需求下,「先做小而专」看起来很慢——UNIX 怎么回应这个质疑。

书里借「第三系统」的行话给出时间观:任何系统都会按三个版本演化30:

  • 第一系统:精瘦,但缺功能;
  • 第二系统:设计者自信了,把上一版没敢加的功能全加上——牺牲性能堆功能(这个现象本身是第 10 章的「第二系统综合征」);
  • 第三系统:删掉没用的,保住真正有用的,性能与功能取得平衡——唯一值得交付的形态

关键结论是:第三系统没有捷径——「再怎么努力跳过前面的步骤,到头来也只是多做了几个 第一系统和第二系统」31。能做的不是跳步,是缩短周期:尽早做出原型(prototype, 验证想法用的快速试做品),用真实反馈代替臆测,让第一系统快点变成第三系统32。 原型怎么打、和「曳光弹」什么区别,是第 09 章的主题——这里先记结论: UNIX 的「小而专」不是慢,是把弯路折叠进快速迭代里。

7. 作者的判断与证据

书里给了证据的:

  • UNIX 半个多世纪的存活与扩散本身是原书的核心证据;「UNIX 中使用的点子在其他软件的开发中得到了应用」被列为哲学普遍价值的证明33;
  • X Window System 只实现机制不做策略,是分离原则可考的真实案例;
  • 管道、标准输入输出(第 3 节那三条默认通道)、文本格式是至今可验证的活机制(本书出版近六十年后仍在原样工作)。

作者的推测与展开,要分开看的:

  • 「写不出单责软件=理解不透」是强经验判断,方向上一直成立,但也可能只是「问题本身不可分」;
  • 十条准则(小写命名、保护森林、劣就是优等)带有明显的 UNIX 文化趣味,是 Gancarz 的立场转述,不宜当工程结论引用。

8. 边界与局限

  • 文本接口有真实代价。 原书承认:复杂二进制格式有时无法避免;把文本拆开读本身有开销, 「运行效率的差距小到可以忽略」在数据量大的今天未必成立——这是 1994 年(Gancarz 原著)的语境。
  • 「小就是美」与「大而全」至今是进行时的争论。 本书只呈现了 UNIX 一侧;企业级单体应用、 平台型产品的成功构成另一侧证据,书内未展开。
  • 管道这套形态依赖「流」式的数据。 现代交互式应用(图形、实时协作)难以塞进「文本进文本出」的插座,书里的 GUI-也是-过滤器论证在这些场景偏弱。
  • 沉默原则与可用性有张力。 对新用户,「什么都不显示」常被体验为「死机了」;书里「两套接口」的补丁(§5)正是为此,但对纯命令行工具并不成立。

9. 可带走的

  1. 写一个新功能前先问:这是一个「新的软件」,还是现有软件的一站管道?
  2. 美化、统计、格式化一律拆出去——主命令只管主业;
  3. 接口选文本、选标准格式(CSV——逗号分隔的表格文本——和 XML 这类通用格式),让数据能被陌生工具直接吃;
  4. 输出守静默:人看得懂的话不是机器吃得下的数据;错误走错误通道;
  5. 行为守最小意外:命名、参数、退出行为向同类工具看齐,最忌「相似但稍有不同」;
  6. 能借就借:先找现成的命令、库、脚本,再考虑写——伟大的程序员能借来好的代码;
  7. 胶水用脚本:连接既有软件的活,交给可移植的解释型语言;
  8. 别给交互式软件留数据死胡同:老用户一定会要一个能被程序调用的口子;
  9. 接受三系统:第一版允许瘦、第二版警惕胖,目标是第三版——省时间靠原型,不靠跳步。

10. 原文地图

主题原书章原文位置
UNIX 思想=设计判断的沉淀3.37 UNIX思想text/43-ch03-37-3-37-unix.txt:45(搜「设计判断」) · text/43-ch03-37-3-37-unix.txt:60(搜「任何软件开发领域」)
工作唯一主张、目录命令例3.57 工作唯一text/63-ch03-57.txt:9(搜「一项工作」) · text/63-ch03-57.txt:12(搜「显示目录内容」)
单责暴露理解、抵御多功能主义3.57 工作唯一text/63-ch03-57.txt:22(搜「本质」) · text/63-ch03-57.txt:39(搜「多功能主义」)
过滤器定义、GUI 也是过滤器3.64 过滤器化text/70-ch03-64.txt:9(搜「过滤器」) · text/70-ch03-64.txt:21(搜「GUI」)
标准输入输出、可连接性3.64 过滤器化text/70-ch03-64.txt:26(搜「标准输入」)
组合产生价值、替换3.40 组合原则text/46-ch03-40.txt:16(搜「能够完成多种多样的任务」) · text/46-ch03-40.txt:20(搜「独立」)
文本流接口、二进制的例外3.40 组合原则text/46-ch03-40.txt:16(搜「文本流」) · text/46-ch03-40.txt:29(搜「二进制」)
小就是美四优点3.56 小就是美text/62-ch03-56.txt:17(搜「易于理解」) · text/62-ch03-56.txt:34(搜「轻量」) · text/62-ch03-56.txt:36(搜「组合」)
大软件的问题3.56 小就是美text/62-ch03-56.txt:53(搜「独立的世界」)
文本文件优势3.60 文本数据text/66-ch03-60.txt:20(搜「最普遍的」) · text/66-ch03-60.txt:30(搜「冗长」)
杠杆效应、借代码格言3.61 充分利用软件的杠杆效应text/67-ch03-61.txt:3(搜「杠杆效应」) · text/67-ch03-61.txt:25(搜「伟大的程序员」)
shell 当胶水3.62 活用shell脚本text/68-ch03-62-3-62-shell.txt:21(搜「解释型语言」) · text/68-ch03-62-3-62-shell.txt:35(搜「胶水语言」)
避开交互式接口3.63 避开交互式用户接口text/69-ch03-63.txt:3(搜「交互式用户接口」) · text/69-ch03-63.txt:29(搜「瓶颈」) · text/69-ch03-63.txt:35(搜「大就是美」)
控制权还给命令解释器、两套接口3.63 避开交互式用户接口text/69-ch03-63.txt:8(搜「命令解释器」)
沉默原则与连接3.48 沉默原则text/54-ch03-48.txt:6(搜「沉默寡言」) · text/54-ch03-48.txt:20(搜「用作其他软件的输入」)
「没有找到文件」对比3.64 过滤器化(扩展)text/70-ch03-64.txt:102(搜「没有找到文件」)
错误通道与冗余开关3.48 沉默原则text/54-ch03-48.txt:33(搜「标准错误」)
最小意外、加号必须加法3.47 最小意外原则text/53-ch03-47.txt:8(搜「感到意外」) · text/53-ch03-47.txt:26(搜「加法」)
get 与 fetch、相似但稍有不同3.47 最小意外原则text/53-ch03-47.txt:49(搜「fetch」) · text/53-ch03-47.txt:39(搜「稍有不同」)
三系统论3.58 尽早创建原型text/64-ch03-58.txt:55(搜「第一系统」) · text/64-ch03-58.txt:67(搜「别无他法」)
原型缩短周期3.58 尽早创建原型text/64-ch03-58.txt:27(搜「缩短发布周期」)
透明性公示性、调试不是附属品3.44 透明性原则text/50-ch03-44.txt:11(搜「公示性」) · text/50-ch03-44.txt:24(搜「绝对不是附」)
表达性:数据优于逻辑3.46 表达性原则text/52-ch03-46.txt:9(搜「数据」) · text/52-ch03-46.txt:17(搜「换算表」)
简单原则:自我表现欲与销售压力3.42 简单原则text/48-ch03-42.txt:18(搜「自我表现欲」) · text/48-ch03-42.txt:30(搜「噱头」)
经济原则3.50 经济原则text/56-ch03-50.txt:39(搜「回本」)
十七条 UNIX 思想清单3.37 UNIX思想text/43-ch03-37-3-37-unix.txt:12(搜「模块化原则」)
哲学九定理清单3.55 UNIX哲学text/61-ch03-55-3-55-unix.txt:9(搜「小就是美」)

Footnotes

  1. 出处:「3.37 UNIX思想」第 45 段(text/43-ch03-37-3-37-unix.txt:45,搜「设计判断」)与第 60 段(text/43-ch03-37-3-37-unix.txt:60,搜「任何软件开发领域」)。

  2. 出处:「3.37 UNIX思想」第 12 段(text/43-ch03-37-3-37-unix.txt:12,搜「模块化原则」)与「3.55 UNIX哲学」第 9 段(text/61-ch03-55-3-55-unix.txt:9,搜「小就是美」)。

  3. 出处:「3.57 工作唯一」第 9 段(text/63-ch03-57.txt:9,搜「一项工作」)。

  4. 出处:「3.57 工作唯一」第 12 段(text/63-ch03-57.txt:12,搜「显示目录内容」)。

  5. 出处:「3.57 工作唯一」第 22 段(text/63-ch03-57.txt:22,搜「本质」)与第 25 段(text/63-ch03-57.txt:25,搜「重复使用」)。

  6. 出处:「3.57 工作唯一」第 39 段(text/63-ch03-57.txt:39,搜「多功能主义」)。

  7. 出处:「3.64 过滤器化」第 9 段(text/70-ch03-64.txt:9,搜「过滤器」)与第 21 段(text/70-ch03-64.txt:21,搜「GUI」)。

  8. 出处:「3.60 文本数据」第 20 段(text/66-ch03-60.txt:20,搜「最普遍的」)。

  9. 出处:「3.64 过滤器化」第 26 段(text/70-ch03-64.txt:26,搜「标准输入」)与第 32 段(text/70-ch03-64.txt:32,搜「可连接性」)。

  10. 出处:「3.64 过滤器化」第 26 段(text/70-ch03-64.txt:26,搜「标准输入」)。管道示例为演示;命令、组合方式与书内 3.57 的目录命令例同构。

  11. 出处:「3.40 组合原则」第 16 段(text/46-ch03-40.txt:16,搜「能够完成多种多样的任务」)与第 20 段(text/46-ch03-40.txt:20,搜「独立」)。

  12. 出处:「3.61 充分利用软件的杠杆效应」第 3 段(text/67-ch03-61.txt:3,搜「杠杆效应」)。

  13. 出处:「3.61 充分利用软件的杠杆效应」第 25 段(text/67-ch03-61.txt:25,搜「伟大的程序员」)。

  14. 出处:「3.62 活用shell脚本」第 21 段(text/68-ch03-62-3-62-shell.txt:21,搜「解释型语言」)与第 35 段(text/68-ch03-62-3-62-shell.txt:35,搜「胶水语言」)。

  15. 出处:「3.62 活用shell脚本」第 22 段(text/68-ch03-62-3-62-shell.txt:22,搜「编译型语言」)。

  16. 出处:「3.48 沉默原则」第 6 段(text/54-ch03-48.txt:6,搜「沉默寡言」)。

  17. 出处:「3.48 沉默原则」第 20 段(text/54-ch03-48.txt:20,搜「用作其他软件的输入」)。

  18. 出处:「3.64 过滤器化」扩展第 102 段(text/70-ch03-64.txt:102,搜「没有找到文件」)。

  19. 出处:「3.48 沉默原则」第 23 段(text/54-ch03-48.txt:23,搜「运行开始和运行结束」)。

  20. 出处:「3.48 沉默原则」第 33 段(text/54-ch03-48.txt:33,搜「标准错误」)。

  21. 出处:「3.47 最小意外原则」第 13 段(text/53-ch03-47.txt:13,搜「学习的东西最少」)。

  22. 出处:「3.47 最小意外原则」第 26 段(text/53-ch03-47.txt:26,搜「加法」)。

  23. 出处:「3.47 最小意外原则」第 49 段(text/53-ch03-47.txt:49,搜「fetch」)。

  24. 出处:「3.47 最小意外原则」第 39 段(text/53-ch03-47.txt:39,搜「稍有不同」)。

  25. 出处:「序章 本书导读」第 169 段(text/04-fm.txt:169,搜「相通」)。

  26. 出处:「3.63 避开交互式用户接口」第 3 段(text/69-ch03-63.txt:3,搜「交互式用户接口」)。

  27. 出处:「3.63 避开交互式用户接口」第 17 段(text/69-ch03-63.txt:17,搜「对话方法」)、第 29 段(text/69-ch03-63.txt:29,搜「瓶颈」)、第 35 段(text/69-ch03-63.txt:35,搜「大就是美」)。

  28. 出处:「3.63 避开交互式用户接口」第 8 段(text/69-ch03-63.txt:8,搜「命令解释器」)。

  29. 出处:「3.63 避开交互式用户接口」第 46 段(text/69-ch03-63.txt:46,搜「初学者」)。

  30. 出处:「第三系统」第 55 段(text/64-ch03-58.txt:55,搜「第一系统」)。

  31. 出处:「第三系统」第 67 段(text/64-ch03-58.txt:67,搜「别无他法」)。

  32. 出处:「3.58 尽早创建原型」第 27 段(text/64-ch03-58.txt:27,搜「缩短发布周期」)。

  33. 出处:「3.55 UNIX哲学」第 27 段(text/61-ch03-55-3-55-unix.txt:27,搜「普遍价值」)。