01 / 导读
导读
引用
Ousterhout, J. (2021). A Philosophy of Software Design (2nd ed.). Yaknyam Press.
章节概要
这是一本只有二十二章、不足200页的小书,围绕一个问题展开:如何把软件分解为类、方法等模块,使系统的复杂性保持在可控范围内。本书源自作者在斯坦福大学讲授的软件设计课程,许多例子取自学生项目,例如一个图形界面的文本编辑器。书末汇总了设计原则,以及正文中陆续提出的"危险信号"(red flags),即设计出问题的警示迹象。据作者介绍,第二版改动不多:新增第21章,改写并扩充第6章(从其他章节移入部分内容),并增加了几节,把本书观点与罗伯特·马丁的 Clean Code 作比较。
第1–3章:Introduction(引言);The Nature of Complexity(复杂性的本质);Working Code Isn't Enough(能运行的代码还不够)
第2章给复杂性下定义,列出它的症状(变更放大、认知负荷、未知的未知)和成因(依赖与晦涩),并指出复杂性是逐步累积的。第3章对比战术式编程与战略式编程,后者是一种投资心态:在设计上预先多花一点时间,以节省日后的时间;本章还讨论应投入多少,包括初创公司的情形。
第4章:Modules Should Be Deep(模块应该是深的)
全书的核心比喻:深模块在简单的接口背后提供强大的功能,浅模块则增加了接口却没隐藏多少东西。本章讨论接口与抽象,告诫不要患上"类炎"(类过多过小),并以Java和Unix的输入输出为例作比较。
第5章:Information Hiding (and Leakage)(信息隐藏(与泄漏))
解释信息隐藏及其失败形态——信息泄漏,其中包括时间分解。一组围绕HTTP服务器的例子讨论了类过多、参数处理以及响应中的默认值。
第6章:General-Purpose Modules are Deeper(通用模块更深)
作者认为,过度专用化可能是软件复杂性的最大单一来源。本章以编辑器项目中的文本类为例,展示如何用通用的API(插入和删除一段范围)取代绑定到特定按键的方法;列出帮助找到平衡点的问题;以设备驱动为例说明如何把专用化向上(或向下)推;把通用的撤销历史与专用的操作分离;并主张消除代码中的特殊情况。
第7–8章:Different Layer, Different Abstraction(不同层,不同抽象);Pull Complexity Downwards(把复杂性往下拉)
第7章把透传方法和透传变量、装饰器以及接口与实现的区别,视为相邻层重复同一抽象的迹象。第8章主张应由模块的实现者而非使用者来吸收复杂性,以编辑器的文本类和配置参数为例,并提醒不要把这一想法推向极端。
第9章:Better Together Or Better Apart?(合在一起还是分开?)
给出合并代码的标准(共享信息、简化接口、消除重复),提供编辑器中的例子,并讨论方法的拆分与合并。新增的一节反驳 Clean Code 关于函数应当非常短的建议:深度比长度更重要。
第10章:Define Errors Out Of Existence(通过定义消除错误)
论证异常是复杂性的重要来源,并说明如何减少它们:把API定义得使错误不会发生(以Windows中的文件删除和Java的substring方法为例),在底层屏蔽异常,集中处理异常,考虑何时直接崩溃是可以接受的,以及通过设计消除特殊情况。
第11章:Design it Twice(设计两次)
篇幅很短的一章,建议设计者在定案之前考虑不止一种设计。
第12–16章:Why Write Comments? The Four Excuses(为什么要写注释?四个借口);Comments Should Describe Things that Aren't Obvious from the Code(注释应描述代码中不明显的内容);Choosing Names(命名);Write The Comments First(先写注释);Modifying Existing Code(修改现有代码)
全书篇幅最大的一组章节讨论文档。第12章回应不写注释的四个常见借口,并在第二版中驳斥 Clean Code 认为注释是失败的观点。第13章说明注释应补充什么:低层次的精确、高层次的直觉、接口文档以及跨模块的设计决策。第14章要求名字精确、一致,能唤起清晰的形象。第15章主张把先写注释作为设计工具。第16章把战略式思维应用于修改现有代码和维护注释。
第17–18章:Consistency(一致性);Code Should be Obvious(代码应该一目了然)
第17章给出一致性的例子、确保一致性的方法,并提醒不要过度追求;第18章列出让代码对读者更明显或更不明显的因素。
第19章:Software Trends(软件潮流)
依据本书的原则,评价面向对象的继承、敏捷开发、单元测试、测试驱动开发、设计模式以及getter和setter。
第20章:Designing for Performance(为性能而设计)
讨论如何思考性能、先测量再修改、围绕关键路径进行设计,并给出一个取自作者RAMCloud系统的较长例子。
第21–22章:Decide What Matters(确定什么是重要的);Conclusion(结语)
新增的第21章把好的设计说成是区分重要与不重要的东西:寻找杠杆点,尽量减少重要的东西,通过显著位置、重复和中心地位来突出重要的东西,并避免两种错误——把太多东西当作重要,以及忽视真正重要的东西。全书以简短的结语收尾。
核心概念
第1–3章:复杂性与心态
- 复杂性:全书所要处理的问题;第2章把它的成因归结为依赖与晦涩。
- 变更放大、认知负荷、未知的未知:复杂性表现出来的三种症状。
- 战略式与战术式编程:持续投资于设计,而不只是让下一个功能跑起来。
第4–10章:模块设计
- 深模块:在简单接口背后隐藏大量功能的模块。
- 信息泄漏:同一设计决策反映在多个模块中,导致一处改动牵连所有这些模块。
- 适度通用:接口通用到足以支持多种用途,实现则满足当前需要。
- 把复杂性往下拉:让实现者而不是每个调用者来处理复杂性。
- 通过定义消除错误:把接口设计得使错误情况根本不会出现。
第11–18章:过程、注释与可读性
- 设计两次:在定案之前考虑其他设计方案。
- 作为设计工具的注释:在写代码之前写注释,让注释塑造设计,而不是事后描述。
- 一目了然的代码:读者无需多想就能很快理解的代码。
第19–22章:潮流、性能与优先级
- 关键路径:在性能关键的情形下必须执行的代码;书中的RAMCloud例子让这段代码中没有多余的方法调用和特殊情况检查。
- 危险信号:标示设计问题的、有名称的症状,汇总在书末。
- 确定什么是重要的:识别设计中少数重要的方面,并围绕它们组织系统。
评析
优点
- 一个思想,贯彻始终。 每一章都回到复杂性及其成因,因此本书容易记住,其建议也便于在代码评审中运用。
- 经过课堂检验的具体例子。 编辑器、HTTP服务器和撤销机制的例子都来自作者的课程,既展示学生有缺陷的设计,也展示更好的设计;危险信号清单把原则变成了可用的检查表。
- 敢于表明立场。 本书反驳了一些常见建议,最明显的是第二版中在方法长度和注释问题上与 Clean Code 的分歧,以及对测试驱动开发、getter和setter等潮流的评价。这给了学生可以对照自身经验检验的观点。
- 篇幅短。 不到200页,可以与项目课程同时阅读而不挤占其他学习。
局限
- 只讲模块层面的设计。 本书关注程序内部的类、方法和接口,不涉及需求、分布式系统规模的架构、测试实践、团队或流程,这些由本课程中的其他书籍讨论。
- 基于一个人的经验。 作者把本书呈现为从自己的教学和系统研发中提炼出的一种哲学,论据是例子和论证而不是实证研究;正如与 Clean Code 的交锋所示,读者可能不同意其中的某些立场。
- 对缺乏项目经验的读者帮助有限。 例子假定读者熟悉类、接口和异常,代码多为Java风格;只有在为一定规模的程序吃过苦头之后,这些建议才最有体会。
- 相对第一版改动很少。 作者本人也说,已有第一版的读者未必需要购买第二版,并把新增内容作为免费摘录公开。
在课程中的定位
在软件工程这门课的书单中,本书讲的是代码本身的设计。最接近的搭档是 Thomas 与 Hunt 的 The Pragmatic Programmer:它以简短话题的形式涵盖范围更广的工作习惯与实践,而 Ousterhout 则深入探讨一个问题。两本书一起读,既有广度,又能获得一套一致的设计词汇。
Sommerville 的 Software Engineering 覆盖从需求到演化的整个生命周期;Software Engineering at Google 讨论组织规模上的文化、流程和工具;Bass、Clements 与 Kazman 的 Software Architecture in Practice 在整个系统和质量属性的层面上讨论设计决策;Jackson 的 Software Abstractions 介绍用Alloy对设计进行形式化建模。Ousterhout 让复杂性的代价变得具体,为学习以上所有内容打下基础,最好尽早阅读,在做一个较大的编程项目期间或之前读。
如何使用本书
本书适合编写并维护过中等规模程序(例如一个较大的课程项目)的学生,以及在职程序员。不要求特定语言,但很多例子使用类似Java的代码。
- 核心: 第1–10章,建立全书通用的词汇(复杂性、深模块、信息隐藏、通用接口、错误)。
- 文档: 第12–15章最直接实用,建议在项目第一次代码评审之前读。
- 选读或稍后再读: 讲潮流和性能的第19–20章,以及作为全书优先级总结的第21章。
阅读时打开自己的代码。每读完一章,就对照书末的危险信号审查一个自己写的模块,并在下一个要写的接口上尝试"设计两次"。与 Clean Code 的比较很适合作为讨论练习:读一读马丁的立场和 Ousterhout 的回应,判断哪一种更适用于你的代码库。
