程序员的世界里从来不缺“能跑的代码”,缺的是三年后还敢改的代码。这期要深读的,是斯坦福教授 John Ousterhout 在 Google Tech Talks 上做的一场演讲,主题就是他的代表作《A Philosophy of Software Design》(软件设计哲学)。
Ousterhout 这个人本身就值得说两句:他是 Tcl/Tk 脚本语言的作者,带出了 Raft 共识算法的研究组,还做过分布式操作系统 Sprite。换句话说,他不是坐在书斋里谈“设计之美”的理论家,而是造过大型系统、也被大型系统折磨过的人。这场演讲回答的,是他认为软件行业几十年都没答好的一个问题:复杂度到底从哪来,又该怎么按住它?
它的稀缺之处在于:市面上讲“代码整洁”的内容很多,但大多停留在命名、缩进、函数长度这类战术层面。这场演讲把镜头拉高了一层——它给你一套可以拿去 code review 的判断标准(模块是“深”还是“浅”),一套可以对抗管理层排期的经济学论证(为什么值得多花 10%~20% 的时间做设计),还有一个反直觉但极其管用的实践(先写注释再写代码)。
一个提醒:本场演讲时长约一小时,播放量在数十万量级(以上述 YouTube 页面实际数据为准),是常年被反复推荐的那种“越老越值钱”的神课。下面我们把它的骨架拆出来。
| 项目 | 内容 |
|---|---|
| 视频标题 | A Philosophy of Software Design |
| 频道 | Google TechTalks |
| 演讲者 | John Ousterhout(斯坦福大学计算机系教授) |
| 时长 | 约1小时 |
| 播放量 | 数十万量级(以YouTube页面为准) |
| 核心主题 | 如何在增量开发中持续压制软件复杂度 |
演讲开场,Ousterhout 没有谈任何花哨的概念,而是给复杂度下了一个非常工程师式的定义:凡是让软件难以理解、难以修改的结构性特征,就是复杂度。 注意,他的判断标准不是“代码写得丑不丑”,而是“下一次改动有多痛”。这个定义妙在它把复杂度从审美问题变成了成本问题。
他给出了三个可以自检的症状。第一个是改动放大(change amplification):本来只想改一个小逻辑,结果要碰十几个文件。第二个是认知负担(cognitive load):要完成一个简单功能,开发者必须先在脑子里装下一大坨背景知识。第三个最阴险——未知未知(unknown unknowns):你根本不知道改这个功能之前,还需要先知道哪些事。前两个症状至少让你痛苦,第三个会让你自信满满地引入生产事故。
延伸分析一下:这三个症状其实是递进的“诊断等级”。改动放大可以靠工具和重构缓解,认知负担要靠文档和抽象消化,而未知未知只有靠结构本身来兜底——这也是为什么他后面把全部希望寄托在模块化设计上。他的原书序言里有句很有名的话,大意是:写软件最大的限制,是我们理解自己所建系统的能力。这不是鸡汤,这是容量瓶颈。
这场演讲里我认为最容易被忽视、但杀伤力最大的一个论断是:复杂度不是某次糟糕的大重构造成的,而是在成千上万次“就这一次,先这样”的小妥协中累积的。
Ousterhout 强调复杂度是增量的(incremental):每一处重复的代码、每一个多出来的依赖、每一次“暂时”绕过的边界问题,单看都微不足道,但它们会复利。等团队察觉的时候,系统已经变成了没人敢动的泥球(big ball of mud)。
这个视角解释了一个日常怪象:为什么项目做到中途,开发速度会神秘地断崖式下跌?不是人变懒了,而是每个人每次改动都在往系统里塞进一点点摩擦力,直到所有改动都被摩擦力吞没。
【延伸分析】这也是我把它类比为“高利贷”的原因:战术性欠债借的时候利率看似为零,但它按每次后续改动的成本计息。技术债最坑的地方不是本金,是复利——第一笔债拖慢你 1%,第一百笔之后你 50% 的工时都在还息。所以他的结论是:对抗复杂度没有“一次性大扫除”这条路,只有日拱一卒的防御。
演讲中花了不少篇幅对比两种程序员心态。战术式编程(tactical programming):目标是让功能尽快跑起来,设计能省则省。战略式编程(strategic programming):把写出“长期结构良好”的代码当成第一目标,功能只是这个过程中的副产品。
Ousterhout 提到,采用战略式做法大约要多投入 10%~20% 的时间。听起来很贵?他算的账是:这点投入换来的是未来每一次改动的提速,几个月内就能回本。而战术式选手里最有战斗力的一种人,他给了个专门的称呼——tactical tornado(战术龙卷风):产出惊人,永远在赶工,一骑车烟过后留下一地没人看得懂的结构。龙卷风本人早已奔赴下一个战场,留下整支球队在废墟里考古。
【延伸分析】这段放在 2026 年的 AI 编程时代看反而更扎心:AI 能把“能跑就行”的代码产出速度放大十倍,也就意味着战术式路线的欠债速度同样被放大十倍。生成代码的成本在跌,理解与维护代码的成本没跌,剪刀差全记在了设计能力的账上。这是视频没说、但我认为今天看这场演讲最 timely 的理由。
全场的核心概念登场:深模块(deep module)。衡量一个模块,看两个东西——接口(interface)和功能(functionality)。最好的模块是接口极小、功能极大:你只需要理解一点点,就能撬动一大片能力。
他举的经典例子是 Unix 的文件 I/O:open、read、write、close,加起来五个系统调用,背后却藏着几十年的机制演进——磁盘块管理、缓冲、缓存、设备驱动,全被这五个词挡在了门外。几十年来底层硬件天翻地覆,这个接口几乎没变。这就是“深”的力量:它把复杂性从接口赶进了实现里,而实现是可以随便换的。
反面教材是浅模块(shallow module):接口复杂度几乎等于实现复杂度。最典型的就是“直通方法”——一个类的方法只是原样转发调用下一个类,你为了干一件小事,被迫理解了整条转发链。他的判断相当锋利:一个类的价值,不在于它有多少行代码,而在于它藏掉了多少复杂度。行数多的模块未必坏,行数少但接口复杂的模块一定坏。
【延伸分析】把这条拿去 code review 立刻能用:别再问“这个类拆得够不够小”,改问“这个类的调用方需要理解几件事”。小的浅模块群,往往比一个大的深模块更糟——这是对“类要拆小”这类教条的精确修正。
深模块怎么造出来?Ousterhout 把根子追溯到了 David Parnas 1972 年的经典论文:模块化的真正标准不是流程图上的先后步骤,而是设计决策的封装——每个模块应该隐藏一个关键决策,比如某个文件的格式、某种缓存的策略。决策藏住了,将来改这个决策时,爆炸半径就被限制在一个模块里。
对应的病灶叫信息泄漏(information leakage):同一个设计知识散落在多个模块里。他举的例子是两个类“都知道”时区怎么处理——哪怕没有显式调用关系,它们也通过共享知识产生了隐式耦合,改时区规则时你会漏掉一处,然后收获一个只在跨时区用户身上复现的 bug。
他还点名了一种常见的设计坏味:时间分解(temporal decomposition)——完全按照程序运行的先后顺序来切模块,先干嘛的 一个包、后干嘛的一个包。听起来自然,实则让“同一个决策”碎了一地。
【延伸分析】信息泄漏最值得警惕的地方是它不违反任何编码规范:命名漂亮、注释齐全、单测通过,但两个模块在心里共享了一个秘密。这种耦合在 review 里几乎不可见,只有当需求变更、需要“同时改五处”时才现形。所以设计评审的正确姿势是模拟变更:问“如果这个规则变了,要动几个文件”,而不是问“这代码好不好看”。
深模块往往还是通用模块,但 Ousterhout 在“通用”这件事上给出了一个很克制的刻度:比当前需求多通用一点点,仅此而已。他反复强调 "somewhat general-purpose" 这个词——为一个模块将来可能承担的角色多留一点弹性,但不要为想象中的未来需求构建庞大的配置系统。
多走半步的理由很实际:通用化逼着你问“这个模块的本质职责是什么”,这一问常常能砍掉接口里不必要的细节,让接口变得更简单、更“深”。多走十步则是另一场灾难——过度设计(over-engineering)会让你背上永远用不到的参数、回调与分支,复杂度反而暴涨。
配套的方法论是 design it twice(设计两遍):对关键模块,别满足于第一个能用的方案,强制自己想出两三个截然不同的设计,对比后再选。第一个方案往往只是“你想到的第一个方案”,未必是权衡后的好方案。成本很低——设计阶段的一张草图纸,比实现阶段的一周重构便宜太多。
演讲中最有争议性、也是 Ousterhout 最不在乎争议的部分:他对注释的态度与《Clean Code》一派针锋相对。后者认为代码应该自文档化、注释是弥补代码失败的补丁;Ousterhout 的立场是——注释承载的是代码无法表达的东西:高层意图、约束背景、为什么这么做。
他更进一步:注释不只是写给别人看的说明书,它是设计工具。书中给的做法相当大胆(他称之为 bold approach):先写注释,再写代码。原因是一个反直觉的反馈回路——如果一段注释你写不清楚,大概率不是文笔问题,而是这个模块的设计本身有问题。职责混在一起的模块,连自我介绍都做不利索。
所以注释难写不是“没时间写文档”的问题,而是复杂度的烟雾报警器:哪里的注释写不出来,哪里的设计就该重想。这个视角把注释从“道德义务”变成了“诊断工具”,我认为是全场对普通开发者实用价值最高的一条。
【延伸分析】这在高方差的时代尤其重要:当代码大量由 AI 生成时,注释是少数能锚定“意图契约”的人类产物——它让下一次改动(不管由人还是模型执行)能校验实现是否偏离初衷。
最后他把前面的零件组装成过程方法论。两种流行的极端都被他否掉了:瀑布式的“先做完所有设计再动手”是错的,因为你早期根本不具备做出正确全局设计的知识;纯敏捷式的“设计边写边说”也是错的,因为没有设计意识的迭代只会让复杂度单调递增。
他的答案是增量式设计(incremental design):设计不是项目前期的一个阶段,而是渗透在每一次迭代里的持续动作。每改一个模块,都顺手让它比原来更深一点、更简单一点。单次投入很小,但因为复杂度是复利累积的,这种持续防御的回报同样是复利的——系统不会腐化,反而会随时间变得更易改,这在软件项目里近乎奇迹。
这句话值得单独抄下来贴在工位上:Working code isn't enough(能跑的代码远远不够)。功能只是及格线,长期结构才是专业工程师的答卷。
flowchart TD
A["软件复杂度<br/>改起来有多痛"] --> B["三大症状"]
A --> C["两大病因"]
B --> B1["改动放大"]
B --> B2["认知负担"]
B --> B3["未知未知"]
C --> C1["依赖过多"]
C --> C2["含混不清"]
A --> D["解药一<br/>深模块"]
D --> D1["小接口"]
D --> D2["大功能"]
D --> D3["信息隐藏"]
A --> E["解药二<br/>战略心态"]
E --> E1["能跑不算完"]
E --> E2["持续投资设计"]
A --> F["解药三<br/>注释先行"]
F --> F1["写不清即坏味道"]
F --> F2["设计两遍"]
看完这场演讲,能立刻落地的动作至少有四个。
第一,给你的 code review 换一把尺子。 把“这个类拆得够不够细”换成“调用方需要理解几件事?如果这条业务规则变了,要改几个文件?”前者催生浅模块群,后者才会催生深模块。一个只有 30 行但接口暴露 8 个参数的类,比一个 300 行但只有 3 个方法的类危险得多。
第二,把注释当作烟雾报警器。 下次在某个模块上注释迟迟写不出来时,别硬憋——那是设计在报警。先试着用三句话讲清这个模块“隐藏了什么决策、对外承诺什么”,讲不清就回去重切模块,而不是重写注释。
第三,给自己留 10%~20% 的设计预算,并学会向上论证。 Ousterhout 的 10%~20% 投资论是一份现成的“技术话术”:你可以直接拿去和排期谈判——这不是追求完美主义,而是为未来所有改动的速度买保险。哪怕谈判只争取到 10%,也足以让系统不再加速腐化。
第四,在 AI 编程时代反过来用这套理论。 生成代码越快,“深接口”就越值钱:让 AI 在你定义的窄接口背后自由实现,人只评审接口与意图(注释),不逐行评审实现。这套工作流的前提,恰好是这场演讲教的东西——你自己得先有能力判断什么是深、什么是浅。
这个视频最大的价值,是把“代码要写好”这种空话,变成了一套可以当天就用、用来判断深浅、诊断坏味道、和排期谈判的具体标尺——而它反复验证的只有一件事:复杂度是按复利计息的债,越早开始还,利息越低。
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
