点关注,不迷路↑↑↑
相关阅读
一、关于这本书
《软件设计的哲学》(A Philosophy of Software Design)是斯坦福大学教授 John Ousterhout 写的一本小书,但它讨论的是一个极大的问题:好的软件设计到底长什么样?
我不把它当教材读,我把它当镜子照。每翻几页就会想起自己写过的那些"当时觉得没问题,三个月后看不懂"的代码。这本书的核心命题很简单:软件设计的终极目标是降低复杂性。所有的原则、技巧、经验法则,最终都服务于这一件事。
二、复杂性:看不见的敌人
Ousterhout 对复杂性的定义非常直觉化:难以理解和修改的系统就是复杂的。 他把复杂性分为两类——系统的复杂性和流程的复杂性。
关键洞见是:复杂性随着软件的生长自然累积,你最多只能控制它的增长速度,不可能真正减少它。能做到"不让复杂性失控"就已经很好了。这不是悲观的论断,而是一种务实的态度:复杂性不是某个具体错误导致的,它是软件生长的副产物。
一个很实用的判断标准——代码 CR 中,如果一个需求要修改十几个文件,但每个文件只改几行,这个系统几乎一定是复杂的。问题不在改动量大,而在"需求简单但变更牵扯面太广"。
还有一个容易被忽视的事实:开发者几乎从不接触的子系统的复杂性,再复杂也不会影响系统的整体复杂性。 所以复杂性的关键不是"有没有复杂的东西",而是"复杂度有没有被隔离在它该在的地方"。
三、深模块与浅模块
这是全书最有操作性的部分。Ousterhout 提出了一个核心概念区分:
- 深模块
功能强大,但接口简单。复杂度被封装在内部,使用者只需要面对一个干净的接口。 - 浅模块
功能简单,但接口复杂。使用者需要理解大量细节才能用对。
那些我们最常听到的"最佳实践"——方法不超过 N 行、类应该尽量小、一个功能拆一个类——如果机械执行,产出的恰恰是大量的浅模块。Ousterhout 把这种现象叫做"类炎"(classitis):类太多,每个类做的事情太少,理解系统需要在大量浅模块之间跳来跳去。
核心原则是:模块应该优先满足 90% 的常用需求。 对于非常用需求,提供单独的方法接口,这样对大部分使用者来说,类的复杂度就是常用功能的复杂度,没有额外的认知负担。
还有一个反直觉的判断:模块拥有简单的接口,比拥有简单的实现更重要。 因为模块的使用者通常远多于开发者,所以复杂度尽可能藏在模块内部,暴露给外部的越少越好。
四、信息隐藏与抽象
"设计就是信息隐藏"这个说法很多人听过,但 Ousterhout 把它推得更深:信息泄露不仅仅是暴露内部数据,还包括多个模块共享的"公共知识"。 比如,如果两个模块都依赖同一个文件格式的知识,这个文件格式本身就成了信息泄露点——改一处就得改另一处。
怎么避免?两个关键做法:
- 设计模块时关注任务类型,而非执行时序。
按时序分解模块(先做 A、再做 B、最后做 C)是一种思维惯性,往往导致模块边界模糊。按"谁负责什么任务"来划分,边界才自然清晰。 - 用稍微通用的方式实现。
功能满足当前需求,但接口设计得通用。这有点像第一性原理——不是问"我现在需要什么",而是问"这件事物的本质抽象是什么"。
判断抽象的直觉标准也很有意思:如果出现同一段重复代码,说明还没有找到合适的抽象。 长度不应该是拆分方法的标准,复杂性才是。拆分会提升理解成本,所以非必要不拆分。每个方法"只做一件事,并且做得完整"就够了。
五、核心设计原则
从笔记中提炼出几条贯穿全书的操作原则:
设计两次。 即使只有一种合理的设计方案,也要主动设计第二次,最好是截然不同的方向。对比两者的弱点——好的设计往往在对比中浮现。一开始第一个想法可能足够好,但随着系统越来越复杂,迟早需要这个习惯。所以提前养成。
避免"直通"。 直通方法(只调用另一个方法其他什么也不做)和直通变量(在不同层级间传递同一个变量而不操作它)都暗示抽象出了问题。直通方法意味着两个类可能职责重叠,直通变量可以用上下文对象来消除。
用投资心态修改代码。 每次改代码时,顺手改进一点设计。不是重构,是"投资"——每个改动都让系统比之前好一点点。
小结
这一篇覆盖了《软件设计的哲学》中关于复杂性和模块设计的思想骨架。下一篇文章将聚焦这本书中更靠近"日常编程"的实践智慧:错误处理、注释与命名、AI 时代的复杂性新问题,以及从软件设计延伸到人生哲学的意外收获。
点关注,不迷路↓↓↓
夜雨聆风