乐于分享
好东西不私藏

《软件设计的哲学》:复杂性的本质与模块化设计

《软件设计的哲学》:复杂性的本质与模块化设计

点关注,不迷路↑↑↑

相关阅读

《哲学家的最后一课》:如何看待死亡

《迷人的材料》读书笔记(二)

《可能性的艺术》:经济、文化与文明冲突

一、关于这本书

《软件设计的哲学》(A Philosophy of Software Design)是斯坦福大学教授 John Ousterhout 写的一本小书,但它讨论的是一个极大的问题:好的软件设计到底长什么样?

我不把它当教材读,我把它当镜子照。每翻几页就会想起自己写过的那些"当时觉得没问题,三个月后看不懂"的代码。这本书的核心命题很简单:软件设计的终极目标是降低复杂性。所有的原则、技巧、经验法则,最终都服务于这一件事。

二、复杂性:看不见的敌人

Ousterhout 对复杂性的定义非常直觉化:难以理解和修改的系统就是复杂的。 他把复杂性分为两类——系统的复杂性和流程的复杂性。

关键洞见是:复杂性随着软件的生长自然累积,你最多只能控制它的增长速度,不可能真正减少它。能做到"不让复杂性失控"就已经很好了。这不是悲观的论断,而是一种务实的态度:复杂性不是某个具体错误导致的,它是软件生长的副产物。

一个很实用的判断标准——代码 CR 中,如果一个需求要修改十几个文件,但每个文件只改几行,这个系统几乎一定是复杂的。问题不在改动量大,而在"需求简单但变更牵扯面太广"。

还有一个容易被忽视的事实:开发者几乎从不接触的子系统的复杂性,再复杂也不会影响系统的整体复杂性。 所以复杂性的关键不是"有没有复杂的东西",而是"复杂度有没有被隔离在它该在的地方"。

三、深模块与浅模块

这是全书最有操作性的部分。Ousterhout 提出了一个核心概念区分:

  • 深模块
    功能强大,但接口简单。复杂度被封装在内部,使用者只需要面对一个干净的接口。
  • 浅模块
    功能简单,但接口复杂。使用者需要理解大量细节才能用对。

那些我们最常听到的"最佳实践"——方法不超过 N 行、类应该尽量小、一个功能拆一个类——如果机械执行,产出的恰恰是大量的浅模块。Ousterhout 把这种现象叫做"类炎"(classitis):类太多,每个类做的事情太少,理解系统需要在大量浅模块之间跳来跳去。

核心原则是:模块应该优先满足 90% 的常用需求。 对于非常用需求,提供单独的方法接口,这样对大部分使用者来说,类的复杂度就是常用功能的复杂度,没有额外的认知负担。

还有一个反直觉的判断:模块拥有简单的接口,比拥有简单的实现更重要。 因为模块的使用者通常远多于开发者,所以复杂度尽可能藏在模块内部,暴露给外部的越少越好。

四、信息隐藏与抽象

"设计就是信息隐藏"这个说法很多人听过,但 Ousterhout 把它推得更深:信息泄露不仅仅是暴露内部数据,还包括多个模块共享的"公共知识"。 比如,如果两个模块都依赖同一个文件格式的知识,这个文件格式本身就成了信息泄露点——改一处就得改另一处。

怎么避免?两个关键做法:

  1. 设计模块时关注任务类型,而非执行时序。
     按时序分解模块(先做 A、再做 B、最后做 C)是一种思维惯性,往往导致模块边界模糊。按"谁负责什么任务"来划分,边界才自然清晰。
  2. 用稍微通用的方式实现。
     功能满足当前需求,但接口设计得通用。这有点像第一性原理——不是问"我现在需要什么",而是问"这件事物的本质抽象是什么"。

判断抽象的直觉标准也很有意思:如果出现同一段重复代码,说明还没有找到合适的抽象。 长度不应该是拆分方法的标准,复杂性才是。拆分会提升理解成本,所以非必要不拆分。每个方法"只做一件事,并且做得完整"就够了。

五、核心设计原则

从笔记中提炼出几条贯穿全书的操作原则:

设计两次。 即使只有一种合理的设计方案,也要主动设计第二次,最好是截然不同的方向。对比两者的弱点——好的设计往往在对比中浮现。一开始第一个想法可能足够好,但随着系统越来越复杂,迟早需要这个习惯。所以提前养成。

避免"直通"。 直通方法(只调用另一个方法其他什么也不做)和直通变量(在不同层级间传递同一个变量而不操作它)都暗示抽象出了问题。直通方法意味着两个类可能职责重叠,直通变量可以用上下文对象来消除。

用投资心态修改代码。 每次改代码时,顺手改进一点设计。不是重构,是"投资"——每个改动都让系统比之前好一点点。

小结

这一篇覆盖了《软件设计的哲学》中关于复杂性和模块设计的思想骨架。下一篇文章将聚焦这本书中更靠近"日常编程"的实践智慧:错误处理、注释与命名、AI 时代的复杂性新问题,以及从软件设计延伸到人生哲学的意外收获。

点关注,不迷路↓↓↓