重读〈软件设计的哲学〉:复杂度才是软件设计的敌人
AI工具

重读〈软件设计的哲学〉:复杂度才是软件设计的敌人

钢镚钢镚
·2026-07-31·8

《软件设计的哲学》只讨论一件事:如何降低复杂度。

作者 John Ousterhout 是斯坦福大学教授,参与过 Tcl、Tk、Sprite 和 RAMCloud 等项目。他发现,很多工程师会写代码、会测试、会重构,却说不清什么是好设计。

他的判断标准很简单:

如果一个设计让系统更容易理解和修改,它就是好设计。

01 复杂度是什么

复杂度不是代码行数,而是人理解和修改代码时付出的成本

它通常表现为三种症状:

  • 变化放大。一个简单需求要修改很多文件。
  • 认知负荷。改一处代码前,必须理解大量上下文。
  • 未知的未知。你不知道哪些地方会受到影响。

第三种最危险。前两种至少知道问题在哪里,未知的未知意味着风险根本看不见。

复杂度主要来自两个根源:依赖和晦涩。

依赖是修改一段代码时,必须同时考虑其他代码。

晦涩是重要信息没有被清楚表达,例如命名模糊、约定隐蔽、设计决策分散。

02 好模块应该足够深

好模块的标准不是小,而是接口简单、功能强大。

Unix 文件系统就是典型的深模块。调用者只需要理解 open、read、write、close,背后复杂的缓存、权限和磁盘管理都被隐藏了。

浅模块恰好相反。接口很多,却没有隐藏多少复杂度。

很多项目中的 Controller、Service、DAO 层层转发,就是典型例子。每一层都只有一行代码,没有产生新的抽象,只增加了跳转成本。

所以,拆分模块不是目的。

真正的问题是:拆分后,调用者需要理解的东西变少了吗?

03 信息隐藏比 private 更重要

信息隐藏不是把字段设为 private,而是把设计决策封装在模块内部

如果两个模块必须同时理解文件格式、业务规则或状态变化,它们即使物理上分开,也依然存在隐性耦合。

一个常见错误是按执行顺序拆模块。例如,把读取文件和解析文件分别放进两个类,但两个类都要理解文件格式。格式一改,两边都要修改。

好的边界应该按照知识划分。

谁需要知道同一个设计决策,谁就更可能属于同一个模块。

04 战略编程胜过战术编程

战术编程只关心功能能不能尽快上线。

战略编程还会考虑,这次实现会不会让未来更难修改。

技术债通常不是一次错误决定造成的,而是无数次“先这样,以后再改”叠加的结果。

作者建议把约 10% 到 20% 的开发时间用于设计。不是提前写完整架构,而是在每次修改时多想一步:接口是否合理,复杂度是否扩散,是否存在更简单的方案。

05 五个值得长期坚持的习惯

第一,把复杂度往下拉。能由模块内部统一处理的细节,不要让每个调用者重复承担。

第二,消除特殊情况。比起到处写异常处理,更好的办法是重新设计接口,让部分异常不再存在。

第三,先写接口注释。注释不是解释代码做了什么,而是说明为什么这样设计。

第四,设计两次。重要决策至少比较两个方案,第一个想到的方案通常不是最好的。

第五,保持一致性。相同问题使用相同命名、结构和处理方式,让读者依靠模式理解代码。

06 AI 时代更需要设计判断

AI 可以快速生成能运行的代码,却未必能判断模块边界是否合理。

它可能制造更多类、更多接口、更多特殊分支,也可能把同一条业务规则复制到多个文件。

因此,代码生成能力越强,工程师越需要判断:

  • 这个模块隐藏了多少复杂度?
  • 这个接口是否比功能本身还复杂?
  • 这次修改会让系统更容易理解,还是更难理解?

未来,代码会越来越便宜。

真正昂贵的,是清晰的边界、稳定的抽象,以及对复杂度的判断力。

评论 (0)

?
0/1

还没有评论,来发第一条吧