读书笔记 · 约 14,935 字 · 预计推送 2 部分

《重构》核心手法精选:坏味道目录与安全小步(结合个人代码库 nightshift 实例) 未开始

目录

题记与说明

精读日期:2026-11-05 | 笔记 ID:knowledge-h6n221ha-1 | 类型:主题研究(原书二手精读 + 个人代码库实证)
主题对象:Martin Fowler, *Refactoring: Improving the Design of Existing Code*(第 2 版,2018)——聚焦两个核心构件:坏味道目录(第 3 章 "Bad Smells in Code")与安全小步重构(行为保持变换 + 测试安全网)。

一、本次精读来源(检索并逐篇读过)

#来源性质本次实际读到的内容
1bliki: Code Smell(Fowler, 2006 修订)原文(Fowler 官网)全文。坏味道的定义、可嗅探性、"不必然是问题"、教学用法
2Refactoring 书籍页(refactoring.com/martinfowler.com)原文(作者自述)全文。重构定义、"too small to be worth doing"、全书结构
3The Second Edition of "Refactoring"(Fowler, 2018)原文(作者写作备忘)全文含各 memo。第二版改动规模、68/10/17 数字、JavaScript 示例、web-first、用 git commit 序列管理重构
4Catalog of Refactorings(refactoring.com/catalog)原文(官方目录)全部条目与标签。本次抓取逐一清点为 66 个重构条目,含别名与 20 个分类标签
5Refactoring Chapter 3 — Bad Smells in Code(Rafael Melo, Medium, 2020)二手(第 3 章摘录式总结)全文。各坏味道的原书要点摘录与多个原句
6bliki: Self Testing Code(Fowler, 2014 修订)原文(Fowler 官网)全文。自测试代码 = 重构的安全网,"bug detector" 比喻

检索补充(未逐页精读,仅搜索结果确认):

个人代码库:D:/CodexData/nightshift(用户自己的夜间知识工作流系统;nightshift_state.py 418 行、nightshift_scheduler.py 315 行、test_nightshift.py 487 行,共约 1220 行 Python)。


二、七维提取

1. 核心问题

两件事,且互为因果:

  1. 何时重构——面对一段"感觉不对"的代码,如何在几秒到几分钟内低成本地判断"这里值得动手"?逐行做设计评审太贵,不判断又会让技术债滚雪球。
  2. 如何安全地重构——"改善内部结构而不改变外部行为"说起来一句话,做起来凭什么不改坏?大规模重写风险人尽皆知,那风险是如何被拆解掉的?

Fowler 对第一问的回答是坏味道目录(一套可快速嗅探的表面信号),对第二问的回答是手法目录 + 小步 + 测试(每个重构都有动机、机械步骤和示例,每步都小到可验证)。

2. 主要主张

"A code smell is a surface indication that usually corresponds to a deeper problem in the system. The term was first coined by Kent Beck while helping me with my Refactoring book." —— Code Smell

两个微妙点:其一,坏味道必须能快速嗅出来("a smell is by definition something that's quick to spot - or *sniffable* as I've recently put it"),Fowler 自己的阈值是"Java 方法超过十几行鼻子就动";其二,坏味道不必然是问题:

"smells don't always indicate a problem. Some long methods are just fine. You have to look deeper to see if there is an underlying problem there - smells aren't inherently bad on their own - they are often an indicator of a problem rather than the problem themselves."

最好的坏味道是"容易发现 + 大概率通向有趣问题",如 Data Class(全是数据没有行为的类):看到它就问"这个类的行为应该是什么",然后开始把行为搬进去。

"Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which 'too small to be worth doing'. However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring - which allows you to gradually refactor a system over an extended period of time."
"as well as building your software system, you simultaneously build a bug detector that's able to detect any faults inside the system. Should anyone in the team accidentally introduce a bug, the detector goes off."

并给出了最狠的一句话:

"Our attitude is to assume that any non-trivial code without tests is broken."

没有测试的旧代码库陷入"恐惧螺旋":怕改坏 → 不敢重构 → 技术债增加 → 更怕改。有测试则进入"良性螺旋":小清理可以安全做 → 代码保持健康 → 加功能越来越快。

"Doing it one smell at a time is a good way of gradually teaching people on the team to be better programmers."

第二版 memo 里 Fowler 再次强调这个用法("designate a 'smell of the week' and get the team to identify and fix examples of that smell",refactoring-2nd-ed)。

3. 方法(书的技术路线)

书的结构本身就是方法(书籍页与第二版文章):

  1. 开场示例:一个完整的小型重构全程,展示"闻味道 → 小步变换 → 跑测试"的循环。第二版把中间一阶段重写时,Fowler 自己发现"照着 Split Phase 的机械步骤做明显更容易"——这是他对"手法目录的 mechanicals 大多数时候好用"的自我验证。
  2. 原则章:重构的定义、两顶帽子、为何小步。
  3. 坏味道章(第 3 章,24 个):每个味道给出"症状 + 为什么是问题 + 该往哪个手法走"。命名从第 1 版到第 2 版有变化(Long Method → Long Function,SwitchStatements → Repeated Switches,新增 Loops、Insider Trading 等)。决定开始与停止与知道手法同样重要:
"Deciding when to start refactoring — and when to stop — is just as important to refactoring as knowing how to operate the mechanics of it." —— 第 3 章摘录
  1. 测试章:自测试代码。
  2. 手法目录(主体):refactoring.com/catalog 每个条目含名称(含一版别名)、草图、动机、机械步骤(mechanics)、示例。本次抓取清点 66 个条目;第二版文章称目录含 68 个重构、印刷版收录 63 个(自限页数裁掉 5 个低优先级条目与 1 个示例,"这些仍可在 web 版读到")。条目按 20 个标签组织(extract、inline、rename、split-phase、immutability、delegation……)。

第二版的一个重要转向:去类中心化。Fowler 解释选 JavaScript 做示例的原因正是它不"整段围绕 class":

"Although that may sound as simple as changing the name of 'Extract Method' to 'Extract Function', it did entail rethinking all aspects of every refactoring... our refactoring needs to be less centered around them, realizing that they can come and go as code is trained into new shapes." —— refactoring-2nd-ed

4. 数据与证据

文本侧(本次逐一读到):

代码侧(个人代码库,本次通读并实测):

5. 结论

6. 局限

7. 对我的启发(结合 nightshift 的可执行项)

(a)nightshift 坏味道盘点(本次通读结果,行号已核对)

坏味道(第 3 章名)位置证据对应手法(catalog 条目)
Long Functionnightshift_state.py:300-388execute() 共 89 行,单函数内分发 scan/verify/commit/fail 四个命令,且每个命令里再按 research/knowledge/repo-audit/kb 四种 kind 分支Extract Function(按命令各抽一个 scan_command 等)
Divergent Change同上每新增一种 kind 或一个命令都要改同一个函数——两种变化原因挤在一起先 Extract Function 分命令,再考虑按 kind 收敛特判
Duplicated Codenightshift_state.py:55-61, 70-75locked() 内 Windows(msvcrt)/POSIX(fcntl) 的加锁与解锁两段平台分支重复出现两次Extract Function(_acquire_lock / _release_lock)
Duplicated Code(近似)nightshift_state.py:334, 353entries = manifest.get("entries", []) if manifest else ... 推导写了两遍且略有差异(334 行多一个 entry_json 分支)先统一为一处,差异显式化
Global Datanightshift_state.py:16KB = Path("G:/我的云端硬盘/...") 硬编码外部路径;测试被迫在 test_nightshift.py:40-41 用 self.m.KB = ... 覆盖模块属性Encapsulate Variable(收进配置/参数,测试不再"扒开模块改常量")
Comments(气味版)nightshift_scheduler.py:39-41route() 里用三行注释记录"2026-11-03 按用户要求 repo-audit 移出轮换"——变更日志写进了代码;时间片阈值(:25/:40)也随之漂移把时间片表提为数据(Replace Magic Literal + 配置外置),注释进 CHANGELOG
Mutable Datanightshift_state.py:241-248, 261-297mark_failure / mark_complete 原地改共享 state 字典并多次 save_state;mark_complete 失败回滚要手工恢复 state[k]["completed"] = False(:295)最小步:收敛"状态写点"到单一出口;暂不上不可变结构
Mysterious Namenightshift_state.py:103, 111inside() / nonempty() 名字看不出"校验路径在允许根内 / 校验非空文件"Rename Function

(b)一次安全小步的完整演练(以 execute() 为例,下一步就能做)

  1. 戴重构帽之前先确认安全网:跑 python -m unittest discover -s D:/CodexData/nightshift/scripts -p test_nightshift.py -v(本次实测 38 tests / 1.483s / OK)。覆盖不到的分支(kb 错误路径)先补测试。
  2. 第一个小步:只抽 scan 分支——把 nightshift_state.py:302-329 的 prepare(kind) + with locked() 块原样搬进 def scan_command(args):,不改一行逻辑。这符合"too small to be worth doing"标准:单看这一步"不值得",但它把 89 行函数砍掉三分之一且零风险。
  3. 验证 + 落盘:跑测试(应 38 绿)→ 这一步的关键。nightshift 目前不是 git 仓库,回退只能靠手工;把 git init + 每小步一 commit 纳入流程,是把 Fowler 写书流程("change the code, run tests" + commit 序列)补齐的最后一块。
  4. 重复:verify_command → commit_command → fail_command 各抽一次,每步之间测试必须全绿;期间任何一步变红,回退的只是"一小步"。
  5. 最后才动结构:四个命令各自成函数后,kind 特判是否要收敛成查找表,此时才看得清——这印证了"重构会训练代码进入新形状"(refactoring-2nd-ed 原句)。

(c)三个工作流层面的启发


附:重要原文引述索引(便于回查)

  1. "A code smell is a surface indication that usually corresponds to a deeper problem in the system." — Code Smell
  2. "smells don't always indicate a problem... they are often an indicator of a problem rather than the problem themselves." — 同上
  3. "applying a series of small behavior-preserving transformations, each of which 'too small to be worth doing'... By doing them in small steps you reduce the risk of introducing errors." — Refactoring 书籍页
  4. "of those 68 refactorings, all but 10 are still present, and I've added 17 new ones" — The Second Edition of "Refactoring"
  5. "I like to keep all my code examples 'live', so that I can change the code, run tests to ensure it still works" — 同上(Reworking examples memo)
  6. "Deciding when to start refactoring — and when to stop — is just as important to refactoring as knowing how to operate the mechanics of it." — 第 3 章摘录(Medium)
  7. "When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous." — 同上
  8. "you simultaneously build a bug detector that's able to detect any faults inside the system" — Self Testing Code
  9. "Our attitude is to assume that any non-trivial code without tests is broken." — 同上
  10. "Old codebases are often terrifying places... you also end up afraid to refactor the system, thus increasing TechnicalDebt, and getting into a steadily worsening spiral" — 同上

附:本次执行记录

← 资料库今日播报 →