最近读了《软件设计的哲学》(A Philosophy of Software Design),这本书中的很多思想深得我心。简单总结一下其内容,重要的内容在最前面。
这本书直指软件工程的核心挑战:管理复杂性 。书中系统性地阐述了复杂性(表现为系统难以理解、修改和预测)是导致开发成本上升、错误增多和进度缓慢的根本原因。作者 Ousterhout 的核心论点是:优秀的软件设计首要目标就是有效控制复杂性,主要手段是消除不必要的复杂性 或将其封装在定义良好的抽象之后。
书中提出的设计原则清晰且具有实践指导意义,挑战了一些所谓的常见实践:
- 推崇深模块(Deep Modules): Ousterhout 的核心观点是模块的价值在于提供强大的功能(面积)同时保持接口简洁(顶边长度)。深模块通过简单的接口隐藏复杂的实现(如 Unix 文件 I/O:
open
,read
,write
,close
),用户学习成本低,获益大。他批评过度追求"小类小方法"可能导致大量浅模块(接口臃肿,功能有限),反而增加系统整体复杂性("类炎症")。 - 倡导通用接口(General-Purpose Modules): 即使模块初始服务于特定需求,也应设计其接口具备一定通用性。避免为每个具体操作创建专用方法(如"插入字符"),转而提供更基础、通用的操作(如"在位置 X 插入字符串")。这种设计通常能产生更简洁、更强大(更深)的抽象,减少重复和特殊逻辑。
- 强调战略性编程(Strategic Programming): 强烈反对"战术性编程"------为快速实现功能而牺牲设计质量。这种行为积累的技术债务会指数级增加后续的复杂性。Ousterhout 提倡"战略性编程":将创建高质量、低复杂性的设计作为首要目标,即使这意味着初期投入更多时间。关注细节,及时重构消除设计异味,为长期高效开发奠定基础。他将只追求短期速度而制造混乱的开发者称为"战术性龙卷风"。
- 实践"设计两次"(Design It Twice): 对于关键设计点,强制探索至少两种显著不同的方案。即使首选方案明确,思考一个(看似)较差的替代方案并进行比较,通常能揭示潜在问题或启发更好的最终设计,所需额外成本相对较低。
- 肯定注释的价值(Comments): 反对"代码即文档"的极端观点。Ousterhout 认为代码本身无法充分表达设计决策的理由 、抽象的意图 以及接口使用的隐含条件 。高层次、抽象的注释是解释复杂性和补充代码信息的关键工具,应注重其不易过时的特性。
- 重视命名与一致性(Naming and Consistency): 清晰、准确、一致的命名是降低认知负担的基础。糟糕的命名导致混淆和错误。系统范围内的一致性允许开发者基于对某部分的理解快速推断其他部分的行为。
- 优先消除错误(Define Errors Out of Existence): 过度依赖抛出异常处理所有边界情况会增加调用方的复杂性。Ousterhout 主张通过设计避免错误条件的产生 ,或在模块内部安全地处理 错误状态而不将复杂性暴露给用户(对比 Python 索引越界抛异常 vs. JavaScript 返回
undefined
)。设计者应承担更多处理复杂情况的责任。
Ousterhout 也对流行方法提出了基于复杂性视角的批评:
- 敏捷开发 (Agile): 可能因过度关注短期功能交付(战术性)而忽视抽象设计,鼓励推迟重要设计决策,导致后期复杂性爆发。
- 测试驱动开发 (TDD): 可能过于聚焦使特定测试通过,而非寻找最优、通用的抽象设计,易产生特化、浅层的解决方案。
- "代码即文档": 忽略了设计理由和抽象意图等代码难以承载的关键信息。
- 实现继承 (Implementation Inheritance): 常导致类间紧耦合和信息泄露,增加复杂性。组合 (Composition) 通常是更简单、更灵活的选择。
此外,Ousterhout 坚信软件设计技能可通过训练提升。
总结
本书提供了对抗软件复杂性的系统性方法和务实原则。Ousterhout 的核心贡献在于将复杂性管理明确为设计的首要目标,并围绕此目标提出了具体、可操作的指导方针(深模块、通用接口、消除错误、战略性编程等)。书中对流行实践的批判也基于其对复杂性的潜在影响。本书强烈推荐给所有希望构建更易理解、更易维护、更健壮软件的开发者,它强调:在复杂性管理上的投入是高效、可持续软件开发的关键。设计虽无绝对法则,但 Ousterhout 的原则为应对这一核心挑战提供了强大的框架。