一、全书概览

Kent Beck 是谁?Extreme Programming 的发明者,JUnit 的联合创建者,Test-Driven Development 的推广者,Agile Manifesto 的 17 位签署人之一。他的上一本让整个行业重新思考开发方式的书,已经是二十多年前的事了。

《Tidy First?》不是一本讲 Clean Code 的书,也不是一本讲 Refactoring 的教科书。它更像是一位从业四十年的老程序员,把自己每天写代码时的小习惯、小判断、小纠结,一条条摊开给你看。

书的核心主张只有一个:在改代码行为之前,先做一点结构上的整理,会让行为变更更安全、更清晰、更快速。 但这个"先"字后面带了一个问号——因为 Kent Beck 并不打算给你一条铁律。他要做的是帮你建立判断力:什么时候该整理,什么时候不该整理,什么时候整理本身就是一种浪费。

全书分三部分:

| 部分 | 主题 | 章节数 | 内容定位 | |------|------|--------|----------| | Part I: Tidyings | "是什么" | 15 章 | 15 种具体的代码整理手法 | | Part II: Managing | "什么时候做" | 6 章 | 整理与行为变更的节奏管理 | | Part III: Theory | "为什么这么做" | 12 章 | 用经济学和设计理论解释整理的价值 |

这本书的另一个特点是短。192 页,每章就几页,看完一章只需要几分钟。但Kent Beck 自己说,他花了三年才写完——不是因为内容多,而是因为他在反复验证每一个观点是否经得起实践检验。

Ham Vocke 在书评中提到,这本书是他给新人推荐 Clean Code 的替代品。原因很简单:Clean Code 太教条了,而 Tidy First? 承认灰色地带,承认"看情况"才是工程实践的真实状态。Kate Marshalkina 则评价说,这本书"为软件设计书籍树立了新标准"——证明可以用简洁实用的方式写设计,而不是堆砌术语和抽象理论。


二、逐章要点

第 1 章:卫语句(Guard Clauses)

"宁可在程序最前面就中止,也不要把错误或不符条件的状况一路带到后面。"

卫语句的原理很简单:遇到不符合条件的情况,立刻 return 或 throw,而不是把整个函数体塞进嵌套的 if-else 里。Kent Beck 把它列为第一个 tidying 手法,因为他认为这是投入产出比最高的可读性改进。

实际操作中,如果保护条件过多(比如超过 3-5 个),Kent Beck 建议把它们抽成一个独立的验证层或校验函数,而不是让主逻辑被一堆前置检查淹没。这在后端开发中特别常见——controller 里堆了十几行 if 判断就返回 400,说明业务规则可能需要更高层的抽象。

| 改进前 | 改进后 | |--------|--------| | 深层嵌套 if-else,缩进 4-5 层 | 提前 return,主逻辑缩进不超过 2 层 | | 错误处理散落在函数各处 | 错误处理集中在函数开头 | | 阅读时需要跟踪所有分支 | 阅读主路径一气呵成 |

  • [ ] 检查最近写的 3 个函数,有没有可以用卫语句消除的深层嵌套
  • [ ] 统计项目中 guard clause 的使用频率,把它当作代码健康度的一个指标

第 2 章:死代码(Dead Code)

"'也许之后会用到'通常是错觉,版本控制系统已经替你保留了历史。"

删掉不会被调用的代码。这不是洁癖,这是在为团队节省理解成本。Kent Beck 的观点很直接:死代码不仅仅是"不好看"的问题——它会误导后来的开发者以为这段代码还在用,甚至基于它来做错误的假设。

YAGNI(You Aren't Gonna Need It)原则在这里得到了最直接的应用。每次删除少量,方便回溯。如果规模很大,先确认没有其他模块或项目依赖,再动手。Kent Beck 特别提醒:删代码比写代码更需要勇气,但 git log 是你最好的安全网。

  • [ ] 用 IDE 的 "Find Usages" 扫描一次项目,找出至少 3 段死代码并删除
  • [ ] 在团队 code review 中,把"删掉死代码"当作正面行为来鼓励

第 3 章:规范化对称性(Normalize Symmetries)

"一致性让读者能预测接下来会怎么写,减少理解成本。"

同一个功能在代码里有三种不同的写法,这在真实项目中太常见了。Kent Beck 说的"对称性"不是什么高深概念——就是统一风格。比如在 JavaScript/TypeScript 中,别一会儿用 function expression,一会儿用 arrow function,一会儿又用 function declaration。

Linter 和 Formatter(ESLint、Prettier)能解决格式层面的统一,但命名风格、设计模式的选择、架构层面的"多种写法并存",需要团队在 code review 中持续推动。Kent Beck 的态度是:工具能做的让工具做,工具做不了的人工管。

  • [ ] 在项目里跑一次 linter,看看有多少不一致的风格警告
  • [ ] 选一个最近有"多种写法并存"的模块,统一成一种风格

第 4 章:新接口,旧实现(New Interface, Old Implementation)

"渐进式替换能减少全面升级的风险,也避免大量一次性重构带来的冲击。"

当你想改一个旧接口但不敢直接动的时候,Kent Beck 建议的做法是:先建一个新接口,让它内部调用旧实现。这本质上就是 Adapter Pattern 的应用场景。

实际案例:你想把一个类库从同步改成异步方法。不要一次性改所有调用方——先加一个新的 async 方法,内部调用旧的同步逻辑,让调用方逐步迁移,等所有人都切过来了再删旧方法。这种方式在大型项目中特别有价值,因为它把一个大爆炸式的变更拆成了可控的渐进过程。

  • [ ] 找一个你一直想重构但没敢动的旧接口,用这种方式做一次渐进式替换
  • [ ] 给旧接口加上 @deprecated 标记,设定一个移除时间表

第 5 章:阅读顺序(Reading Order)

"大部分开发者花最多时间在阅读代码,好的结构能大幅减少理解和维护的障碍。"

调整代码和文件的组织结构,让人能按"自然的理解路线"去读。Kent Beck 的建议包括:核心逻辑放前面,细节和辅助函数放后面;多文件的项目可以用数字前缀的文件夹名(01-Setup, 02-Validation)来指示执行顺序。

这个 tidying 的出发点是一个被大量研究证实的事实:程序员 70% 以上的时间在读代码,而不是写代码。花 10 分钟调整文件和函数的排列顺序,可能为后来者省下数小时的困惑。

  • [ ] 打开你最近频繁修改的一个文件,问自己:"一个新人从第一行读到最后,能顺畅理解吗?"
  • [ ] 如果项目有多个模块,给目录结构加一个 README 或顺序说明

第 6 章:内聚顺序(Cohesion Order)

"如果两段代码总是一起改,就该放在一起。"

把经常一起修改的代码集中放在一起,提高内聚度。Kent Beck 用了一个很实用的判断标准:你上次改功能 A 的时候,是不是也同时改了文件 B?如果是,它们应该靠得更近——要么放在同一个文件夹里,要么放在同一个类的相邻位置。

如果耦合关系太复杂、无法在当下根治(因为大范围重构的成本太高),Kent Beck 的建议是先做"集中管理"——至少让相关的代码放在同一个地方,降低维护时来回切换的成本。这是务实主义的体现:与其追求完美的架构,不如先让现状变得可控。

  • [ ] 回想最近一次改 bug 时需要同时修改的文件列表,它们是不是散落在不同目录?
  • [ ] 把总是一起改的代码移到同一个文件或目录下

第 7 章:声明与初始化放一起(Move Declaration and Initialization Together)

"能用 const 就不用 let。变量越早被赋值,读者越早能确定它的用途。"

变量的声明和初始化分开写是很多老代码的通病。Kent Beck 建议把它们放在同一个位置——如果某个分支才需要这个变量,就在分支内声明,不必在外层"空声明"。

这个改动看起来微不足道,但实际效果很显著:读者不需要在代码中上下翻找"这个变量什么时候被赋值的"。配合 const 的使用(能用 const 就不用 let),可以消除一大类"变量被意外覆盖"的 bug。

  • [ ] 在最近写的代码里搜索 let 关键字,看看有多少可以改成 const
  • [ ] 搜索"声明和初始化分开超过 10 行"的情况,合并它们

第 8 章:解释变量(Explaining Variables)

"命名就是最好的注释。"

面对复杂表达式,把它拆成几个有意义的中间变量。Kent Beck 给了一个经典例子:

// Before
const result = someLongCondition(a, b) && anotherCheck(c, d);

// After
const isValidA = someLongCondition(a, b);
const isValidB = anotherCheck(c, d);
const result = isValidA && isValidB;

变量名本身就说明了这段逻辑在干什么。但 Kent Beck 也提醒了反面:别把每个小步骤都拆成变量,那反而增加了阅读负担。判断标准是——拆出来的变量名是否真的增加了理解,还是只是把一行代码变成了三行废话。

  • [ ] 找一个超过 80 字符的复杂条件表达式,用解释变量拆分
  • [ ] 检查拆出来的变量名是否真的解释了"为什么",还是只解释了"做了什么"

第 9 章:解释常量(Explaining Constants)

"每个数字后面都可能隐藏着一条业务规则。"

消灭魔法数字。if (req.ConcurrentReq > 10) 里的 10 是什么意思?改成 const MAX_CONCURRENT_REQ = 10 之后,代码自己就会说话了。

Kent Beck 还建议对错误码和状态码使用枚举(Enum),让语义更清晰。这个手法的好处不只是可读性——当你需要改这个数字的时候,只需要改一个地方,而不是在整个项目里地毯式搜索。

  • [ ] 在项目中搜索纯数字字面量,挑出至少 5 个替换为命名常量
  • [ ] 为常用的错误码或状态码创建一个统一的常量文件或枚举

第 10 章:显式参数(Explicit Parameters)

"函数签名应该让人一眼看出它需要什么,而不是让人猜。"

Kent Beck 反对用一个大 Object 或 Map 传入函数,然后在函数内部解构出需要的字段。他的建议是:上层函数负责收集参数,然后明确地传给下层函数。这样函数签名本身就是文档,IDE 也能提供更好的提示。

但在 TypeScript/JavaScript 的实践中,2-3 个以上的参数确实适合用对象参数(配合类型定义),这比位置参数更不容易出错。Kent Beck 的核心观点不是"永远用位置参数",而是"让调用方清楚地知道传了什么"。

  • [ ] 检查项目中有多少函数接收了一个模糊的 optionsparams 对象却没有类型定义
  • [ ] 给它们加上 TypeScript 接口定义,或者拆分成更明确的参数

第 11-15 章:分块语句、提取辅助函数、合并回一块、解释注释、删除冗余注释

这五章覆盖了一组"小而实用"的整理手法:

  • 分块语句(Chunk Statements):用空行或简短注释把逻辑相关的代码分块。Kent Beck 认为空行是代码中最便宜的"段落标记",用好它能让长函数变得可读。

  • 提取辅助函数(Extract Helper):把独立且用途明确的代码抽成函数。但 Kent Beck 特别警告:别产生一堆 helper1helper2 这种毫无意义的命名。

  • 合并回一块(One Pile):这是全书最有"反直觉"色彩的一章——Kent Beck 承认"小就是好"并非绝对真理。过度拆分会造成参数爆炸、上下文难以追踪。有时候,把碎片化的代码合并回一个较大的块,反而更容易看清全貌,再决定怎么合理地拆分。

  • 解释注释(Explaining Comments):注释的价值在于说"为什么这样做",而不是"这段代码做了什么"。代码本身已经说了它做了什么。

  • 删除冗余注释(Delete Redundant Comments):和代码内容重复的注释比没有注释更糟糕——它会让人养成不看代码只看注释的习惯,而注释很容易过时。

  • [ ] 在一个长函数中用空行分块,每块前面加一行注释说明这块在干什么

  • [ ] 找一段被过度拆分成 5+ 个小函数的代码,尝试合并回一个函数再重新审视

  • [ ] 搜索项目中的 // TODO// FIXME,看看有多少已经过期了

第 16 章:分离整理(Separate Tidying)

"把整理和功能变更放在不同的 commit 或 PR 里。"

Kent Beck 的这个建议直接影响了 CI/CD 流程的设计。如果整理和行为变更混在一个提交里,reviewer 根本分不清哪些是重构、哪些是新功能。出了 bug,回滚的风险也更大。

具体做法:小规模、多次提交,每次提交只做一类事情。这让 git blame、git bisect 这些工具真正有用武之地。在高频开发的团队中,这个原则能显著减少合并冲突和意外破坏。

第 17 章:连锁整理(Chaining)

"整理是软件设计的薯片——吃了一片就停不下来。"

Kent Beck 用了一个很生动的比喻:Tidyings are the Pringles of software design。你开始整理一段代码,发现可以加卫语句,加了之后发现条件可以抽成辅助函数,抽完之后发现命名不够规范……一个整理引发另一个整理,形成连锁反应。

这种连锁反应在实务中非常常见,Kent Beck 不否认它的价值。但他同时警告:你必须知道什么时候该停下来。整理的目的是为了让下一步的行为变更更容易,不是为了追求"完美代码"。如果你整理了两个小时还没开始写新功能,你大概率已经过头了。

第 18 章:批量大小(Batch Sizes)

"每次改动太大,review 困难、合并冲突多;太小,又造成过多的提交负担。"

Kent Beck 建议根据团队和项目的实际情况选择合适的批量。如果团队能快速 review,小批量最有效;反之可以稍微放大批量以减少合并次数。关键判断标准:改动是否保持在"可以一次性理解"的范围内。

第 19 章:节奏(Rhythm)

"如果一次整理超过一两个小时,你已经进入大规模重构的领域了。"

Kent Beck 认为,小规模整理应该只需要几分钟到一小时。如果经常超时,说明代码的设计问题已经超出了"整理"能解决的范围,需要更系统的规划。

第 20-21 章:理顺纷乱、优先级四象限

面对整理和行为变更交织在一起的情况,Kent Beck 给了三个选项:直接提交(快但乱)、拆成多个 PR(更干净但费时)、放弃当前进度重新整理(最完整但成本最高)。他不做价值判断,只是帮你分析每种选择的代价。

第 21 章提出了一个四层优先级框架:

| 优先级 | 含义 | 适用场景 | |--------|------|----------| | First | 先整理再改行为 | 整理能显著降低行为变更的风险和成本 | | After | 先改行为再整理 | 行为变更紧急,整理可以稍后补上 | | Later | 等到相关需求来了再整理 | 收益不明确,不值得现在投入 | | Never | 永远不整理 | 代码不会再变动,也没有学习价值 |

  • [ ] 下次遇到需要改动的旧代码时,先花 30 秒判断它属于 First/After/Later/Never 中的哪一类
  • [ ] 记录你的判断和实际结果,一个月后回顾判断是否准确

第 22-33 章:理论基础

后半部分是全书最有"分量"的内容。Kent Beck 用软件设计的基本概念(耦合、内聚)和经济学原理(时间价值、期权理论)来解释为什么整理有价值。

他的核心论点:软件的价值不在于你已经做了什么,而在于你还能做什么。 代码结构越好,你实现下一个功能的速度就越快、成本就越低。这和金融中期权的概念完全对应——期权就是"在未来某个时间以约定价格买入/卖出的权利",它本身有价值,因为你保留了一个选择。

Kent Beck 引入了几个经济学概念来支撑这个论点:

  • 时间价值:今天赚到的一块钱比明天赚到的一块钱更有价值(因为可以再投资)
  • 期权价值:保留未来选择的权利本身就有价值
  • 现金流 vs 期权:有时候你不应该追求"现在就赚到钱",而应该花钱购买"未来能赚到钱的能力"

把这些概念映射到软件设计中:整理代码就是在购买"未来更容易改代码"的期权。但Kent Beck 也承认,不是所有的期权都值得买——你需要判断这段代码未来被修改的概率有多大。如果一段代码永远不会变(比如一个一次性脚本),花时间整理它就是在浪费钱。

关于耦合和内聚,Kent Beck 的观点和 Larry Constantine 在 1970 年代提出的理论一脉相承,但他的表述更加务实:降低耦合的目标不是为了追求"完美的解耦",而是为了让变更的影响范围可控。 有时候适度的耦合反而能减少代码量,提升理解效率。


三、关键概念速查

1. Tidying(整理)

对代码结构(不是行为)做小幅度、增量式的改进。目的是让下一步的行为变更更安全、更快速。Kent Beck 强调:整理不改变程序的外部行为,所有测试在整理前后应该保持通过。

2. 结构变更 vs 行为变更

这是全书最基础的二元分类。结构变更是重组代码的内部组织方式(重命名、提取函数、调整顺序),行为变更是改变程序对用户呈现的功能。Kent Beck 的核心建议是:把这两类变更分开处理,每次只做一种。

3. 期权性(Optionality)

保留未来选择的权利。在软件设计中,这意味着保持代码的灵活性和可修改性。Kent Beck 认为,好设计的本质就是"让未来变更的成本更低"——你买的不是现在的功能,而是未来的可能性。

4. 耦合(Coupling)

两个模块之间的依赖程度。高耦合意味着改一个模块很可能需要同时改另一个。Kent Beck 的观点比传统教科书更务实:完全消除耦合既不可能也没必要,目标是让耦合变得"可控"和"可见"。

5. 内聚(Cohesion)

一个模块内部各元素之间的相关程度。高内聚意味着模块内的代码都在做同一件事。Kent Beck 的判断标准很实际:如果你改一个功能时总是一起改某几个文件,它们应该有更高的内聚度(放在同一个地方)。

6. 时间价值(Time Value of Money)

经济学概念在软件设计中的应用:今天花 30 分钟整理代码带来的收益(下次改动省下 2 小时),比三个月后花 2 小时处理由混乱代码引发的 bug 成本更低。早整理,早受益。

7. Constantine 等价

Larry Constantine 的理论遗产:好的设计是在耦合和内聚之间找到平衡。Kent Beck 认为这个等价关系依然成立,但需要在实际项目中做出务实的取舍,而不是追求理论上的最优解。


四、核心框架/模型

"是什么 → 什么时候 → 为什么" 三层模型

这是全书的组织框架,也是 Kent Beck 建议的思考路径:

┌─────────────────────────────────────────┐
│  WHY(为什么)                           │
│  经济学论证:期权价值 + 时间价值           │
│  设计理论:耦合 + 内聚                    │
├─────────────────────────────────────────┤
│  WHEN(什么时候)                        │
│  First → After → Later → Never           │
│  节奏控制:分钟级,不是天级               │
│  批量管理:小到可以一次理解               │
├─────────────────────────────────────────┤
│  WHAT(是什么)                          │
│  15 种具体整理手法                        │
│  卫语句、死代码、解释变量……              │
│  结构变更 ≠ 行为变更                      │
└─────────────────────────────────────────┘

整理决策树

Kent Beck 没有画决策树,但根据书中的内容可以推导出这样的判断逻辑:

  1. 这段代码我需要改吗?

    • 不需要 → Never(不整理)
    • 需要 → 继续
  2. 整理后会让改动更安全或更快吗?

    • 不会 → After(先改行为,后整理)
    • 会 → 继续
  3. 整理能在 30 分钟内完成吗?

    • 不能 → Later(等合适时机)
    • 能 → First(先整理)
  4. 整理过程中是否引发了连锁反应?

    • 是 → 设定时间上限(比如 1 小时),到时间就停
    • 否 → 继续直到完成

"整理的薯片效应"控制模型

开始整理
    ↓
完成一个小整理 ──→ 还需要更多整理吗?
    ↓ 是              ↓ 否
下一个整理       停下来,开始行为变更
    ↓
超过时间上限?
    ↓ 是
强制停止,记录剩余整理项

五、金句摘录

  1. "Software design is an exercise in human relationships." 软件设计本质上是在处理人际关系。代码是写给人的,不是写给机器的。

  2. "Tidyings are the Pringles of software design." 整理是软件设计的薯片——吃了一片就停不下来。你必须有意识地控制自己。

  3. "I was mostly getting paid for what I could do next." 我以为我是因为已经完成的工作获得报酬,其实我是因为"接下来还能做什么"而获得报酬。

  4. "Leave the code a little better than you found it." 让你离开时的代码比你到来时好一点。这是最务实的工程师信条。

  5. "The question mark in Tidy First? is a direct response to dogmatic design books." 书名中的问号是对那些教条式设计书的直接回应——没有银弹,只有判断力。

  6. "A dollar today is better than a dollar tomorrow." 今天的一块钱比明天的一块钱更值钱——早整理的收益可以用更长的时间产生复利。

  7. "Earning more money, sooner, with greater likelihood is good. Spending less money, later, with less likelihood is good." 这是 Kent Beck 对软件投资策略的总结:早赚、多赚、大概率赚;晚花、少花、小概率花。


六、行动清单

每天(Daily)

  • [ ] 写新代码前,花 2-5 分钟扫描即将修改的区域,做最小必要的整理(加个卫语句、换个变量名、拆一行长表达式)
  • [ ] 提交代码前检查:有没有可以删的死代码?有没有可以删的冗余注释?
  • [ ] 遇到深层嵌套的函数,先问自己:能用卫语句减少一层缩进吗?
  • [ ] 给整理和行为变更分开提交,不要混在一个 commit 里

每周(Weekly)

  • [ ] 选一个最近频繁修改的模块,花 30 分钟做一轮系统性的整理(统一命名风格、调整阅读顺序、集中高内聚代码)
  • [ ] 回顾本周的 commit 历史,统计整理提交和功能提交的比例。理想比例因项目而异,但如果整理提交为 0,说明你在积累技术债
  • [ ] 用 "First / After / Later / Never" 四象限评估本周遇到但没有整理的代码区域,记录在一个地方

每月(Monthly)

  • [ ] 做一次项目级的代码健康检查:用 linter 扫描全项目,统计技术债指标(循环复杂度、重复代码率、死代码数量)
  • [ ] 回顾本月的整理决策记录,看看"Later"和"Never"的判断是否正确——那些说"以后再整理"的代码,后来真的整理了吗?
  • [ ] 在团队内部分享一个"整理案例":你遇到了什么混乱的代码,做了什么整理,整理后的行为变更变得多容易

七、一句话总结

别追求完美代码,追求"比昨天好一点的代码"——在改功能之前花几分钟整理结构,小改进能换来大回报,但要知道什么时候该停下来。


八、读者热议

"这是 Clean Code 该有的样子"

Ham Vocke 在详细书评中直言:过去几年他犹豫是否推荐 Robert Martin 的《Clean Code》给新人,因为它"太武断、太教条、太旧了"。而《Tidy First?》成了他的新推荐——不因为整理手法更好(其实差不多),而是因为它承认了"看情况"这个工程师每天都在面对的现实。书名里的问号就是最好的态度宣言。

"期权理论改变了我的设计观"

Kate Marshalkina 在书评中特别强调了第三部分(理论篇)的价值。Kent Beck 把金融中期权的概念引入软件设计——"保留选择的权利本身就有价值"——这个视角让她重新理解了为什么有时候花时间做"看起来没用"的整理其实是在投资未来。她也引用了 Kent Beck 的原话:"What behavior can I implement next has value all on its own."——你能实现什么下一个行为,这本身就值钱。

"二十年的经验被一本书系统化了"

台湾开发者 Will 保哥(连续 15 年微软 MVP)在读完此书后表示"越看越兴奋",因为书中很多整理习惯和他二十多年的开发经验高度吻合,但 Kent Beck 把这些零散的直觉系统化成了 32 个可以传授的心法。他的评价是:"有种为什么没早点看到这本书的感觉。"这种共鸣在资深开发者群体中非常普遍——Kent Beck 不是在教你新东西,而是在帮你把一直在做但说不清楚的事情说清楚。


笔记生成:2026-04-28 by 喵喵 🐈


相关笔记

同作者仅有此篇 同主题: