来源:John Ousterhout《A Philosophy of Software Design》 提炼时间:2026-04-24
模块指任何具有接口和内部实现的代码单元: - 函数、类、包、服务都算模块 - 模块化把系统切分成不同部分,每个部分可以独立理解和修改
系统复杂性来自三个地方:
深模块 = 简单接口 + 大量隐藏复杂性
┌─────────────────┐
│ 简单接口(3个) │ ← 调用者只需要知道这3个操作
├─────────────────┤
│ │
│ 复杂内部实现 │ ← 大量复杂性对调用者隐藏
│ │
└─────────────────┘
经典例子:Unix I/O
- 接口:open read write close(4个操作)
- 内部:处理磁盘、网络、终端、内存映射…所有设备差异全部隐藏
- 对调用者来说,读文件和读网络完全相同
经典例子:GC(垃圾回收) - 接口:分配内存、释放内存(自动) - 内部:追踪引用、标记存活对象、压缩内存…全部隐藏 - 程序员不需要知道内存碎片如何整理
浅模块 = 简单接口 + 简单实现
┌─────────────────┐
│ 简单接口(10个) │ ← 调用者要理解10个接口
├─────────────────┤
│ 简单实现 │ ← 没什么复杂性被隐藏
└─────────────────┘
调用者承担了理解模块协作关系的认知负担,而不是模块内部。
一个类试图做得太多,导致接口和实现一样浅。典型例子:Java 早期很多类的 get/set 把所有内部状态直接暴露。
技术组件(消息队列、缓存、安全、日志)属于通用模块,应该有简洁但强大的接口,把复杂性藏在内部。
业务模块有时候故意设计得浅一点——因为业务逻辑本身就是多变的,隐藏太多反而增加理解成本。
设计新模块时,先想接口,不先想实现。接口是模块和调用者之间的契约,接口错了内部再好也没用。
检验标准:能用一句话描述模块接口代表什么抽象吗?如果描述不清,接口可能设计过度了。
每个模块应该隐藏: - 如何实现的决策 - 内部数据结构和算法 - 性能优化手段 - 依赖的第三方库
不应该隐藏: - 模块的职责边界 - 明确的输入输出格式 - 复杂度级别(让调用者知道这里有复杂性)
Matt Pocock 指出:AI 生成代码是概率采样,不是逻辑推导。越是遵循基本原则(深模块、接口清晰),AI 越能稳定输出高质量代码。
如果代码库烂(浅模块、依赖混乱),AI 生成的代码只会复制这个烂结构,然后放大烂结构的问题。
1. 接口即合同 AI 编程时,清晰的接口定义等于给 AI 的指令。当模块边界清晰,AI 能更准确地推断哪个模块该放哪个逻辑。
2. 深模块减少 AI 的认知负担 如果模块是浅的,AI 需要同时考虑多个模块的交互,错误率上升。深模块把复杂性隔离,AI 只需要知道"用它就行"。
3. 测试即验证 TDD 的价值在 AI 时代被放大——AI 生成代码可能看起来对但实际错,行为测试能快速发现偏差。
拿到一个模块,问:
如果任何一个回答"是",可能是浅模块信号。
参考:John Ousterhout, "A Philosophy of Software Design", 2018