《代码整洁之道》读书笔记
Published in:2025-07-20 | category: 读书笔记

整洁的代码不是某种特定的风格或模式,而是一种让代码易于理解和修改的状态。本文梳理《代码整洁之道》中关于简单设计、重构、代码坏味与命名的核心知识点。

一、迭进式设计(Simple Design)

Kent Beck 提出,遵循以下四条规则,设计就能变得简单。四条规则按优先级从高到低排列:

1
2
3
4
① 运行所有测试
② 消除重复
③ 保证表达力
④ 尽可能减少类和方法的数量

1.1 运行所有测试

这是驱动整个设计演进过程的基石和动力。系统必须是完全可测试的。

追求高测试覆盖率本身就在强迫你改善设计——因为一个巨大、混乱、依赖众多的类是极难测试的。为了让代码易于测试,你会自然而然地写出短小、单一职责、低耦合的类和函数。

有了测试,就能放心地递增式重构代码:

  • 添加几行代码后,暂停,琢磨变化了的设计。
  • 设计退步了吗?清理它,运行测试,确认没有破坏任何东西。
  • 测试消除了”清理代码会破坏功能”的恐惧。

1.2 消除重复

重复是良好设计中潜藏的最大敌人。

重复的三种常见形式:

重复类型示例解决方向
代码重复多处出现相同的逻辑片段提取方法或工具类
结构重复多个类有相似的字段和操作提取父类或接口
逻辑重复不同实现但本质相同的算法模板方法模式、策略模式

1.3 保证表达力

软件项目的主要成本在于长期维护,而不是最初的开发。

表达力的体现:

  • 好命名:变量、函数、类的名字应该自解释,读代码像读文章。
  • 短小的函数:每个函数只做一件事,函数名就是对这件事的完整描述。
  • 规范的代码风格:统一的格式让团队成员能快速阅读彼此的代码。
  • 选用合适的设计模式并说明:使用 CommandStrategy 这样的模式名称,本身就是一种表达。

1.4 尽可能减少类和方法的数量

这条规则的优先级最低,是在满足前三条的前提下才考虑的。在消除了重复、保证了表达力之后,如果某些类或方法过于琐碎、可以合理合并来简化设计,那么就应该这样做。


二、逐步改进(Successive Refinement)

优秀的软件设计并非一蹴而就,而是源于对代码质量的持续关注和大量小规模、安全的改进。

重构的触发点

当代码功能完整,但新增支持两种类型后,代码到达了一个临界点——任何进一步的修改都会变得困难且容易引入错误。这就是启动重构的信号。

小步重构的五个特征

特征说明
小步严谨每个步骤非常小,可能只是移动一个方法、重命名一个变量
测试保障每一步重构后立即运行测试,确保微小改动不破坏现有功能
持续进行重构不是独立活动,而是在添加功能或修复 Bug 时同步进行
目标驱动最终目标是让代码清晰、可读、易于修改、没有重复
勇气与耐心有勇气改动”能工作但丑陋”的代码,有耐心接受逐步完善的过程

重构时机判断

1
2
3
4
5
6
7
8
9
10
✅ 该重构的信号:
- 添加新功能需要改动多个地方(霰弹式修改)
- 看懂一段代码需要很长时间
- 修改一个功能频繁引入新 Bug
- 函数超过 20 行,类超过 200 行

❌ 不该重构的时机:
- 临近发布节点,稳定优先
- 没有测试覆盖的代码(重构前先补测试)
- 计划废弃的代码

三、味道与启发(Smells and Heuristics)

3.1 一般性问题

暴露时序耦合

当一组函数必须按特定顺序调用时,应通过函数参数类型让时序关系显式可见,防止调用者乱序调用。

1
2
3
4
5
6
7
8
9
// ❌ 三个函数独立,调用者无法知道必须按此顺序执行
getUp();
dressUp();
work();

// ✅ 通过返回值类型传递状态,强制按顺序调用
AwakeMan man = getUp();
DressedMan dressed = dressUp(man);
work(dressed);

封装边界条件

边界条件处理代码(如 + 1- 1)容易散落在各处,应将其集中封装,避免重复和遗漏。

函数内语句应在同一抽象层级

1
2
3
4
5
6
// ✅ renderHtml() 只包含同一层级的子步骤
void renderHtml() {
renderHeader();
renderBody();
renderFooter();
}

用命名常量替代魔法数字

1
2
3
4
5
// ❌ 含义不明
if (status == 2) { ... }

// ✅ 意图清晰
if (status == OrderStatus.PAID) { ... }

名称应该说明副作用

1
2
3
4
5
// ❌ get 暗示只读取,但实际上可能创建对象
getSession();

// ✅ 名称清晰表达了"有则取、无则创"的行为
getOrCreateSession();

3.2 注释问题

废弃的注释:过时、无关或不正确的注释比没有注释更糟糕,会误导读者,立即删除。

冗余注释:注释应该谈及代码没有提到的信息,而不是重复代码已经表达的内容。

1
2
3
4
5
6
7
// ❌ 冗余
// 将 i 加 1
i++;

// ✅ 有价值——解释"为什么"而不是"是什么"
// 使用位运算替代取模,性能提升约 3 倍(需保证 size 是 2 的幂)
index = hash & (size - 1);

注释掉的代码:直接删除它,版本控制系统会帮你记住历史。


3.3 函数问题

过多的参数

参数数量评价
0 个最佳
1 个良好
2 个尚可
3 个以上应该重构,封装为参数对象
1
2
3
4
5
// ❌
void createUser(String name, int age, String email, String phone, String address) { ... }

// ✅
void createUser(UserCreateRequest request) { ... }

标识参数(Flag Arguments)

布尔类型的参数大声宣告函数做了不止一件事,应该拆分为两个函数。

1
2
3
4
render(true);          // ❌ 含义不清

renderForSuite(); // ✅
renderForSingleTest(); // ✅

3.4 命名问题

名称与抽象层级相符

1
2
void writeDataToSocket() { ... }  // ❌ 暴露实现细节
void send() { ... } // ✅ 反映抽象意图

尽可能使用标准命名法

用途惯用前缀/动词
工厂方法createoffrombuild
类型转换toasvalueOf
布尔判断ishascanshould
查询findgetqueryfetch
执行动作executerunprocesshandle

四、核心原则总结

维度核心原则记忆关键词
设计简单设计四原则(测试→消除重复→表达力→减少数量)测试先行,逐步演进
重构小步前进,测试保障,持续进行不积跬步,无以至千里
函数短小、单一职责、少参数、无副作用只做一件事
注释解释”为什么”而非”是什么”,及时清理废弃注释注释是代码的补充,不是替代
命名自解释、无歧义、与抽象层级相符好名字胜过好注释

“让营地比你来时更干净。”——童子军规则,也是代码整洁的精髓。

Prev:
《Elasticsearch:The Definitive Guide》读书分享
Next:
阿里巴巴 Java 开发手册学习总结(三):MySQL 规约与工程规范