《软件设计的哲学》(第2版)原书大约190页,中文版219页,原书第一版豆瓣评分9.2分。本书深入探讨了软件设计中的核心问题:如何将复杂的软件系统分解为可以相对独立实现的模块(例如类和方法),从而降低其复杂性并提高开发效率。
关于作者
来自前言部分作者对于本书的阐述:
你凭什么认为你知道软件设计的所有答案?老实说,我不知道。在我学习编程时,并没有软件设计方面的课程,也没有导师教我设计原则。在我学习编程时,代码评审几乎不存 在。我对软件设计的想法来自编写和阅读代码的亲身经历。在我的职业生涯中,我用各种语言编写了大约 25 万行代码。我所在的团队从零开 始创建了 3 个操作系统、多个文件和存储系统、基础工具(如调试器、 构建系统和图形用户界面工具包)、脚本语言,以及文本、绘图、演示 和集成电路的交互式编辑器。一路走来,我亲身经历了大型软件系统的 各种问题,并尝试了各种设计技术。此外,我还阅读了大量其他人编写 的代码,这让我接触了各种好的和坏的方法。
从所有这些经验中,我试图总结出一些共同点,既要避免错误,又要使用技巧。本书反映了我的经验:书中描述的每一个问题都是我亲 身经历过的,建议的每一种技巧都是我在自己的编码工作中成功使用 过的。
《软件设计的哲学》(第2版)
关于本书
本书深入探讨了软件设计中的核心问题:如何将复杂的软件系统分解为可以相对独立实现的模块(例如类和方法),从而降低其复杂性并提高开发效率。本书首先介绍了软件设计中的基本问题,即复杂性的本质。其次,讨论了有关如何处理软件设计过程的“哲学”问题,如通用设计的重要性、与《代码整洁之道》中设计哲学的对比,以及如何将重要的东西和不重要的东西区分开等内容。最后,总结了在软件设计过程中应遵循的一系列设计原则,以及一系列识别设计问题的警示信号。
本书适合软件工程师、计算机科学专业的学生、教育者、对软件设计和开发感兴趣的自学者和技术管理者阅读。通过应用本书中的思想,读者可以最大限度地降低大型软件系统的复杂性,从而更快地以更低的成本编写软件,并构建更易于维护和增强的系统。
如何使用本书
本书有两个总体目标。第一个是描述软件复杂性的本质,即“复杂 性”是什么意思、为什么重要,以及如何识别程序是否存在不必要的复 杂性。本书的第二个目标(也是更具挑战性的目标)是介绍在软件开发 过程中可以将复杂性最小化的技术。遗憾的是,并不存在一个简单的秘 方能够保证设计出优秀的软件。作为替代,我将介绍一系列更高层次的 哲学思想,如“类应该深”或“定义错误不存在”。这些思想可能无法 立即确定什么是最佳设计,但你可以使用它们来比较各种设计方案,并 指导你探索设计空间。
本书描述的许多设计原则都有些抽象,因此如果不查看实际代码, 可能很难理解。要找到小巧的例子纳入本书的篇幅,同时又足以用实际 系统来说明导论中的问题,这是一个挑战(如果你发现好的例子,请发 给我)。因此,要掌握这些原理的应用,光看本书可能还不够。
使用本书的最佳方法是结合代码评审。当你阅读别人的代码时,想想它是否符合本书讨论的思想,以及这与代码的复杂性有什么关系。看别人的代码比看自己的代码更容易发现设计问题。你可以使用本书描述的警示信号来识别问题并提出改进建议。评审代码还能让你接触到新的 设计方法和编程技巧。
提高设计技能的方法之一是学会识别“警示信号”:这些信号表明 一段代码可能没必要那么复杂。在本书中,我将指出一些警示信号,揭 示与每个主要设计问题相关的问题;最重要的警示信号总结在本书末尾。然后,你可以在编码时使用这些提示:当你看到警示信号时,停下 来,寻找一个可以消除问题的替代设计。刚开始采用这种方法时,你可 能需要尝试多种设计方案,才能找到一种能消除警示信号的方案。不要 轻易放弃:在解决问题之前尝试的替代方案越多,你学到的东西就越 多。随着时间的推移,你会发现代码中的警示信号越来越少,设计也越 来越简洁。你的经验也会让你看到其他警示信号,你可以利用这些警示 信号来识别设计问题(我很乐意听取你的意见)。
在运用本书的观点时,一定要适度和谨慎。每条规则都有例外,每 条原则都有限制。如果你将任何设计理念发挥到极致,很可能会落得一 个糟糕的下场。优美的设计反映了相互竞争的想法和方法之间的平衡。 本书有多个小节的标题是“过犹不及”,其中介绍了如何识别好东西是 否过犹不及。
本书中几乎所有的示例都是用 Java 或 C++ 编写的,而且大部分讨 论都是在面向对象语言中设计类。不过,这些观点也适用于其他领域。 几乎所有与方法相关的思想也可以应用于没有面向对象特征的语言(如 C 语言)中的函数。这些设计思想也适用于类以外的模块,如子系统或 网络服务。
有了这些背景知识,我们就可以详细讨论导致复杂性的原因,以及 如何让软件系统变得更简单。
详细目录
第 1章 导言 001
1.1 如何使用本书 004
第 2章 复杂性的本质 007
2.1 复杂性的定义 007
2.2 复杂性的表现 009
2.3 复杂性的原因 012
2.4 复杂性是增量的 014
2.5 结论 015
第3章 能工作的代码是不够的 017
3.1 战术性编程 017
3.2 战略性编程 019
3.3 投资多少? 020
3.4 初创企业与投资 022
3.5 结论 023
第4章 模块应该深 025
4.1 模块化设计 025
4.2 接口包含哪些内容? 027
4.3 抽象 028
4.4 深模块 029
4.5 浅模块 031
4.6 类炎 033
4.7 示例:Java和UNIX I/O 033
4.8 结论 035
第5章 信息隐藏(和泄漏) 037
5.1 信息隐藏 037
5.2 信息泄漏 039
5.3 时序分解 040
5.4 示例:HTTP服务器 041
5.5 示例:类过多 042
5.6 示例:HTTP参数处理 043
5.7 示例:HTTP响应中的默认值 045
5.8 类内的信息隐藏 046
5.9 过犹不及 047
5.10 结论 047
第6章 通用模块更深 049
6.1 让类有点通用 049
6.2 示例:为编辑器存储文本 051
6.3 更通用的API 052
6.4 通用性带来更好的信息隐藏 054
6.5 要问自己的问题 055
6.6 将专用性向上推(和向下推) 056
6.7 示例:编辑器撤销机制 057
6.8 消除代码中的特例 060
6.9 结论 061
第7章 不同层,不同抽象 063
7.1 直通方法 064
7.2 接口重复何时可行? 066
7.3 装饰器 067
7.4 接口与实现 069
7.5 直通变量 070
7.6 结论 073
第8章 降低复杂性 075
8.1 示例:编辑器文本类 076
8.2 示例:配置参数 076
8.3 过犹不及 078
8.4 结论 078
第9章 合并好,还是分开好? 079
9.1 如果共享信息,则合并 081
9.2 如果可以简化接口,则合并 081
9.3 消除重复,则合并 082
9.4 区分通用代码和专用代码 085
9.5 示例:插入光标和选择区域 086
9.6 示例:单独的日志类 087
9.7 拆分和连接方法 089
9.8 不同意见:《代码整洁之道》 092
9.9 结论 093
第 10章 避免处理异常 095
10.1 为何异常会增加复杂性 095
10.2 异常太多 098
10.3 定义错误不存在 100
10.4 示例:Windows中的文件删除 100
10.5 示例:Java的substring方法 101
10.6 异常屏蔽 103
10.7 异常聚合 104
10.8 就让它崩溃 109
10.9 过犹不及 110
10.10 结论 111
第 11章 设计两次 113
第 12章 为什么要写注释?4个借口 117
12.1 好的代码自己就是文档 118
12.2 我没有时间写注释 119
12.3 注释会过时,会产生误导 120
12.4 我见过的注释都没有价值 121
12.5 写好注释的好处 121
12.6 不同观点:注释就是失败 122
第 13章 注释应描述代码中不明显的内容 125
13.1 选择约定 126
13.2 不要重复代码 127
13.3 低层注释增加精确度 130
13.4 高层注释增强直观性 133
13.5 接口文档 136
13.6 实现注释:做什么和为什么,而不是怎么做 144
13.7 跨模块设计决策 146
13.8 结论 149
13.9 13.5节问题解答 150
第 14章 选择名称 151
14.1 示例:糟糕的名称会导致缺陷 151
14.2 塑造形象 153
14.3 名称应精确 153
14.4 一致地使用名称 157
14.5 避免多余的词 158
14.6 不同意见:Go风格指南 159
14.7 结论 161
第 15章 先编写注释 163
15.1 拖延的注释是糟糕的注释 163
15.2 先编写注释 164
15.3 注释是一种设计工具 165
15.4 早期注释是有趣的注释 166
15.5 早期注释是否昂贵? 167
15.6 结论 168
第 16章 修改现有代码 169
16.1 持续使用战略性编程 169
16.2 维护注释:让注释靠近代码 171
16.3 注释属于代码,而非提交日志 172
16.4 维护注释:避免重复 173
16.5 维护注释:检查差异 175
16.6 更高层次的注释更容易维护 175
第 17章 一致性 177
17.1 一致性的例子 177
17.2 确保一致性 178
17.3 过犹不及 181
17.4 结论 181
第 18章 代码应显而易见 183
18.1 让代码更显而易见 184
18.2 让代码不显而易见的因素 186
18.3 结论 190
第 19章 软件发展趋势 191
19.1 面向对象编程和继承 191
19.2 敏捷开发 193
19.3 单元测试 194
19.4 测试驱动开发 196
19.5 设计模式 197
19.6 取值方法和设值方法 197
19.7 结论 198
第 20章 性能设计 199
20.1 如何考虑性能 199
20.2 修改前(后)的度量 202
20.3 围绕关键路径进行设计 203
20.4 示例:RAMCloud的Buffer类 204
20.5 结论 210
第 21章 确定什么是重要的 211
21.1 如何确定什么是重要的? 211
21.2 尽量减少重要的东西 212
21.3 如何强调重要的东西 213
21.4 错误 213
21.5 更广泛的思考 214
第 22章 结论 215
设计原则总结 217
警示信号总结 219