书 · 2008-08 · 罗伯特·C. 马丁(Robert C. Martin)

《代码整洁之道》

计算机与 AI书单第 33 名

一句话结论

代码能运行只是起点;马丁主张通过清楚的表达、明确的职责和测试支持下的持续重构,让软件长期保持可理解、可修改。

值不值得读完整本?

值得读完整本,尤其适合已经写过程序、开始维护自己或他人代码的人:原则与重构案例合起来,才能看清作者怎样判断代码质量,但具体规则需要结合语言、需求和性能要求使用。

  • 先读第1至5章,理解作者为什么重视可读性,再读第9章和第14章,把测试与逐步改进连起来。
  • 细读第6至8章、第10至12章,重点理解对象与数据结构的取舍、错误处理、依赖边界和简单设计。
  • 第13章应读懂共享状态与执行交错的风险;它是并发问题的入门提醒,不能替代系统的并发编程教材。
  • 第14至16章至少完整跟做一个重构案例;其余案例初读可略过部分代码细节,第17章适合随后查阅。
  • 配合一段有测试的旧代码练习:每次只改一个问题,观察理解成本和修改范围是否下降,不要只背规则。

核心观点

1. 代码首先要让维护它的人读懂

作者把代码看作写给其他程序员的表达,强调有意义的命名、清楚的组织和一致的格式。注释应补充必要的信息,而不是长期替难懂的代码作解释。

为什么:修改程序前,人通常必须先读懂已有代码,因此阅读上的阻力会反复转化为开发成本。含糊名称迫使读者在脑中翻译,误导性名称还会让人依据错误理解作修改。清楚的命名和组织能把意图直接放进代码,减少这种额外推理。重复代码表面意思的注释则多出一份需要同步维护的说明,一旦过时,反而会制造误解。

2. 函数应围绕一件事,在同一抽象层次上表达

作者偏爱短小、名称明确、参数较少的函数,要求函数围绕一个清楚的任务组织。他还主张把查询与改变状态的操作分开,避免隐藏的副作用。

为什么:一个函数同时混合业务步骤和底层细节时,读者必须不断切换理解层次。把细节提取为名称明确的操作,上层流程就能按任务顺序被理解。参数越多,调用者需要同时掌握的条件和组合通常越多,因此作者倾向于减少参数。隐藏的状态变化还会制造调用顺序上的依赖,让看似局部的操作影响别处的行为。

3. 按变化原因划分职责,用边界限制影响范围

类的大小应主要按承担多少职责判断,单一职责意味着只有一个引起它变化的原因。作者还主张隐藏实现细节、隔离外部依赖,并把系统的构造与使用分开。

为什么:互不相关的职责放在一起,会让一种需求的修改触碰另一种需求依赖的代码,扩大理解和验证的范围。把共同变化的内容放在一起、把不同变化原因分开,可以减少这种牵连。让业务依赖自己需要的抽象,而不是第三方组件的全部细节,也能限制组件升级或替换造成的影响。不过,第6章明确讨论了取舍:对象通常便于增加新类型,数据结构配合过程通常便于增加新操作,选择取决于预计怎样变化。

4. 错误处理应保持主流程清楚,并服务于调用者

作者倾向于用异常表达失败,让错误处理与正常业务流程分开,并提供足够的错误上下文。他反对随意返回或传入空值,也强调按调用者的处理需要组织异常。

为什么:返回错误码往往要求调用者在每一步检查结果,正常流程容易被分支淹没,检查也容易遗漏。异常可以把失败路径移到专门的处理位置,但仍需说明失败发生时的操作和情境。若多种底层异常对调用者意味着同一种应对方式,封装后统一表达可以减少重复处理。对于可以正常表示的空结果,返回空集合等明确结果能减少散落各处的空值检查,不必把正常情况都当作异常。

5. 用可靠测试支持持续重构,让设计逐步变好

测试既用于发现回归,也是程序员持续修改结构的保障;测试代码本身同样需要保持清楚、易维护。作者主张在测试支持下逐步消除重复、表达意图,并减少不必要的设计元素。

为什么:缺少及时反馈时,程序员很难判断结构调整是否破坏了已有行为,于是容易回避清理,让问题继续积累。快速、独立、可重复且能自动判断结果的测试,可以缩短这条反馈链。测试如果难读又脆弱,维护负担会让团队放弃它,重构保障也随之减弱。测试驱动开发通过很小的编写与验证步骤推动实现,再留出整理结构的机会。第12章的简单设计规则把通过测试放在首位,然后考虑消除重复、充分表达和减少类与方法,避免为了形式简短牺牲前面的目标。

6. 并发需要专门设计,关键是控制共享状态与交错

作者要求把并发控制与其他职责分开,尽量限制共享数据,并谨慎处理同步、执行模型和线程退出。并发正确性不能只靠普通测试偶尔运行成功来判断。

为什么:顺序执行时看似完整的一步操作,在线程之间可能被拆开并交错执行,从而产生单线程下不会出现的结果。共享可变数据越多,需要推理的交互也越多,因此减少共享和缩小数据访问范围能降低复杂性。同步需要围绕完整的协作关系设计,分别保证几个方法安全,并不自动保证连续调用它们也是安全的。并发故障还可能只在罕见的调度顺序下出现,因此作者强调在不同负载和执行条件下测试,并认真调查偶发失败。

反对意见

拆得更小未必更容易理解,注释也有独立价值(John Ousterhout)

Ousterhout在《A Philosophy of Software Design》第二版以及与马丁的直接讨论中主张:模块应通过简单接口隐藏较多复杂性,过度拆分可能让读者频繁跳转、记住更多关系。他也认为接口约定和设计理由常常需要注释,不能仅靠名称表达。2025年公开的讨论记录了双方的分歧,也记录了马丁承认可能过度拆分、初版缺少识别这种情况的充分指导。

语言背景和用户需要不能被通用规则盖住(Adam Tornhill)

Tornhill在《Review of Clean Code》中质疑,本书有时把围绕Java形成的建议提升为普遍原则,不同作者的建议与案例也存在不一致。他还指出,错误处理需要从用户遇到什么问题、需要怎样恢复出发,不能只把异常和日志安排得整齐。这个批评提醒读者:内部结构清楚,并不自动意味着失败时的产品行为合理。

结构规则可能与具体性能目标冲突(Casey Muratori)

Muratori在《“Clean” Code, Horrible Performance》中通过C++图形面积计算案例,比较虚函数类层次、分支与查表方案,指出某些抽象和数据组织方式会妨碍优化。他反对仅凭代码符合多态、封装或拆分规则就判定设计更好。不过,该案例的性能结果取决于程序与测试条件,不能推导出所有整洁代码实践都会降低性能。

消除重复也可能提前制造错误抽象(Dan Abramov)

Abramov在《Goodbye, Clean Code》中回顾一次重构:合并重复后,代码虽然更短,却更难适应后续需求,也影响同事协作。他据此主张根据实际演化和协作结果判断抽象的价值。文章没有直接评论马丁或本书,但它具体挑战了把消除重复当作自动改进的读法。

相关推荐

导读由 AI 根据公开资料整理,没有读取原书全文,一切以原著为准。导读 Primer 首页