解锁软件设计的奥秘:《A Philosophy of Software Design》书籍

《A Philosophy of Software Design》由 John Ousterhout 撰写, 主要探讨了软件设计中的复杂性问题, 并提出了一系列设计原则和实践指导, 旨在帮助开发者减少软件复杂性, 提高代码的可维护性和可扩展性.
这本书的作者 John Ousterhout 可是业界大咖, 他有着丰富的编程经验, 参与过众多大型项目, 像创建 Tcl
脚本语言, 在分布式操作系统和存储系统领域也成绩斐然. 正是这些宝贵的经验, 让他在书中分享的见解深刻又实用.
书籍要点
1. 复杂性的定义与表现
书中开篇就点明, 复杂性是软件设计的头号难题. 它就像软件里的"暗物质", 看不见摸不着, 却处处影响着软件开发的效率和质量.
- 复杂性的定义: 只要是跟软件系统结构相关, 让系统难以理解和修改的东西, 都是复杂性的表现.
- 复杂性的表现:
- 变更放大: 简单的修改需要多处代码改动
- 认知负荷: 开发者需要掌握大量信息才能完成任务
- 未知的未知: 不清楚哪些代码需要修改或需要哪些信息
- 复杂性的原因: 复杂性主要由依赖关系和模糊性引起. 依赖关系是指代码之间的相互依赖, 模糊性是指重要信息不明确或难以理解.
2. 战略编程 VS 战术编程
- 战术编程: 以快速完成任务为目标, 通常会导致代码质量下降, 复杂性逐渐积累. 战术编程只盯着眼前功能实现, 不管以后的事, 这样会让系统越来越复杂.
- 战略编程: 注重长期代码质量, 通过投资时间来改进设计, 减少复杂性. 战略编程的核心思想是 “代码只是能运行还不够”, 开发者应优先考虑系统的长期结构, 而不是仅仅完成当前任务.
作者建议我们在开发过程中, 要不断地做小投资来优化设计, 比如花 10% - 20%的开发时间来思考设计问题, 这样既能保证项目进度, 又能提升软件质量.
3. 模块设计
- 模块化设计: 将系统分解为相对独立的模块, 每个模块有明确的接口和实现. 模块的接口应尽可能简单, 隐藏复杂的实现细节.
- 深度模块: 深度模块是指功能强大但接口简单的模块. 深度模块通过隐藏复杂性, 减少了系统的整体复杂性.
- 浅模块: 浅模块是指接口复杂但功能有限的模块, 通常会增加系统的复杂性.

对于一个模块来说: 接口(Interface)越少越好; 功能(Functionality)越多越好.
4. 信息隐藏与泄漏
- 信息隐藏: 模块应隐藏其内部实现细节, 只暴露必要的接口. 信息隐藏可以减少依赖关系, 简化接口.
- 信息泄漏: 当设计决策在多个模块中反映时, 会导致信息泄漏, 增加系统的复杂性. 应尽量避免信息泄漏, 确保每个模块只负责特定的功能.
5. 通用模块 vs. 专用模块
- 通用模块: 通用模块的接口应设计得足够通用, 能够适应多种使用场景. 通用模块通常比专用模块更简单, 更易于维护.
- 专用模块: 专用模块的接口通常与特定功能紧密耦合, 容易导致信息泄漏和复杂性增加.
6. 不同层次的抽象
- 不同层次的抽象: 系统中的每一层应提供不同的抽象, 避免相邻层次的抽象过于相似. 如果相邻层次的抽象相似, 通常意味着模块分解存在问题.
- 传递方法: 传递方法是指只调用其他方法而不做任何额外工作的方法, 通常表明模块的职责划分不清晰.
7. 将复杂性向下推
- 将复杂性向下推: 模块应尽可能处理内部的复杂性, 而不是将复杂性暴露给使用者. 模块的接口应尽可能简单, 即使这意味着模块的实现会变得更复杂.
8. 代码的合并与分离
- 合并代码: 如果多个模块共享信息或功能, 通常应将它们合并为一个模块, 以减少接口复杂性和信息泄漏.
- 分离代码: 如果模块的功能过于复杂或职责不清晰, 应考虑将其拆分为多个模块.
9. 消除错误
- 消除错误: 通过重新定义 API 的语义, 减少异常和错误处理的需求. 例如, 某些操作可以通过默认行为避免抛出异常.
- 异常屏蔽: 在低层次模块中处理异常, 避免将异常传播到高层次模块.
- 异常聚合: 将多个异常处理逻辑合并为一个统一的处理机制, 减少重复代码.
10.设计两次
- 设计两次: 在设计模块或系统时, 应考虑多个设计方案, 并比较它们的优缺点. 通过设计两次, 开发者可以更好地理解问题, 并选择最佳的设计方案.
11.注释的重要性
- 注释的作用: 注释不仅仅是解释代码, 它们还帮助隐藏复杂性, 提供抽象. 注释应描述代码中不明显的信息, 而不是重复代码.
- 注释的层次: 注释可以分为接口注释(描述模块的接口和行为)和实现注释(描述代码的内部实现). 接口注释应尽可能简洁, 避免暴露实现细节.
实践指导
软计的核心目标是减少复杂性. 通过遵循书中的设计原则, 开发者可以创建更简单, 更易维护的系统. 复杂性是不可避免的, 但通过良好的设计实践, 可以有效地控制和管理复杂性.
- 避免浅模块: 模块的接口应尽可能简单, 隐藏复杂的实现细节.
- 信息隐藏: 模块应隐藏其内部实现, 减少依赖关系.
- 设计通用模块: 模块的接口应设计得足够通用, 能够适应多种使用场景.
- 将复杂性向下推: 模块应尽可能处理内部的复杂性, 而不是将复杂性暴露给使用者.
- 消除错误: 通过重新定义 API 的语义, 减少异常和错误处理的需求.
- 设计两次: 在设计模块或系统时, 应考虑多个设计方案, 并比较它们的优缺点.
参考资料
Tags: