架构不是玄学,架构领域也有通用的语言和词汇。这些语言很大一部分来自早期黑客对编译器实现、操作系统方案与计算机网络应用的探索,至今仍深刻影响着面向对象、函数式编程与微服务设计。其中最重要的一支,被 Unix 代码贡献者 Douglas McIlroy 誉为 Unix 哲学(Unix Philosophy)——Linux 正是遵循这套设计思想的产物。
本模块提炼出其中的六大思想,它们既是理解 Linux 的钥匙,也是与架构师沟通的通用语言,并映射到面向对象、微服务与工程实践中。
Unix 系设计哲学,都在和单体设计(Monolithic Design)与中心化唱反调。Linux 是社区产品,开发者来自全世界,天然反对中心化——一个由巨大开发团队管理的东西,一定不能是 Mono(单体)的:
这个思想在编程范式中的映射:面向对象用组合替代继承(继承是 Mono 的强耦合,组合是轻量级复用);函数式编程的 Monad 让对象与计算函数可组合,实现最小粒度复用;而 Unix 用管道组合进程,同样是"最小粒度复用程序"。
上图为组合性设计思想的体现:无论是代码仓库的独立发布、文件系统各目录可替换、编程范式上用组合替代继承,还是用管道组合进程,其核心都是"反对单体、反对中心化,以最小粒度实现复用"。这是 Unix 哲学的基石。
Douglas McIlroy 的著名论断:一个应用的输出,应该是另一个应用的输入——这句话道出了计算的本质。
计算就是"把一个过程的输出交给另一个过程作为输入"。这也解释了为什么在设计流计算、管道运算、Monad、泛型容器体系时,我们总希望计算过程间保持相似性(如统一的泛型类型)——只有输入输出形态一致,才能自由组合。
Linux 上 ps aux | grep java、cat access.log | awk '{print $1}' | sort | uniq -c 都是管道的日常实践。
上图为管道设计的核心:进程 1 的标准输出作为管道的输入,供给进程 2 的标准输入,进程间只需保持输入输出形态一致(统一的泛型类型)即可自由组合。这正是"一个应用的输出是另一个应用的输入"的直观体现。
Unix 哲学中有一条非常"硬核"的准则:每个应用只做一件事情,并且做到极致;当程序过于复杂时,就去重构甚至重写它,而不是在原应用上继续加功能。
这与"大而全"的商业策略是否相悖?见仁见智,但事实给出了答案:短视频做成独立应用(抖音)、游戏做成独立应用(王者荣耀),都获得了巨大成功。
以大型系统开发的经验看:宁愿重新做微服务,也不愿重构巨大的单体系统——把新功能做进新系统,比在巨型系统上不断迭代更省成本、效果更好。这也是微服务时代不可逆的原因之一。
即使必须在原有系统上加功能,也应多多重构:重构能看到业务逻辑中的隐患,让程序员更熟悉系统。程序的复杂度不随需求线性增长,需求超过临界值后复杂度呈指数上升——控制复杂度是软件工程的核心问题。
上图为"一事极致、复杂就重构/重写"的决策路径:当某个程序复杂度超标时,若新功能是独立诉求就做进新系统或微服务,若旧系统核心必要则进行重构,若累计复杂度实在过高则考虑重写;无论哪种,目标都是控制复杂度,避免在巨型单体上无限叠加功能。
优秀的架构师常说:程序写复杂了,就是写错了。Unix 哲学的建议是:先用几周构造一个简单版本,发现复杂了就重写它。
很多程序员宁愿长年累月"熟练"地重复劳动,也不愿把重复劳动工具化:
Unix 哲学强调:即便是临时需要,也应该尽可能构造工具来减轻工作。Git 本身就是这样的产物——Linux 内核团队的商业代码管理工具到期后,缔造者们自主研发了 Git,不仅推进了研发,还做成了巨大的开发者生态。
作为程序员,不仅要完成工作,还要重视中间过程的工具缔造——写小型的 ORM 框架、缓存引擎、业务容器,让开发效率越来越高。
上图为工具化决策路径:遇到重复劳动时,先判断是否高频、可沉淀;低频率一次性任务手动完成即可;高频或反复出现的任务则优先构造工具(脚本、Docker 镜像、自动化测试等)并沉淀为可复用资产——Git 正是工具缔造的典范。
拿数据主导规则:数据结构足够好,计算方法自然反映系统逻辑;编程的核心是构造好的数据结构,而不是算法。文件描述符抽象文件、页表抽象内存、Socket 抽象连接——数据结构深刻反映系统本质(自证明代码)。
| 原则 | 含义 |
|---|---|
| 不要猜测瓶颈,要证明瓶颈 | 瓶颈总出现在意料之外的地方,多写性能测试、构造压测场景 |
| 业务规模小不用花哨算法 | 简单算法往往更快;规模大了再测试证明所需算法 |
| 数据主导规则 | 数据结构足够好,计算方法自然反映系统逻辑;编程的核心是构造好的数据结构,而不是算法 |
| 自证明代码 | 文件描述符抽象文件、页表抽象内存、Socket 抽象连接——数据结构深刻反映系统本质 |
时间复杂度只是增长关系,一个算法在某个场景中是否可行,要以实际执行数据为准——这是架构选型中常被忽视的一点:
# 用压测/实测数据证明瓶颈,而非猜测
ab -n 10000 -c 100 http://your-server/ # 简单压力测试
wrk -t4 -c200 -d30s http://your-server/ # 更细粒度压测
perf stat ./your-program # 观察真实执行指标最后记住 Unix 设计者 Ken Thompson 的打破一切规则的规则:搞不定就用蛮力——首先要把事情搞定,否则一切哲学都毫无意义。