题记与说明
精读日期:2026-11-05 | 笔记 ID:knowledge-h6n221ha-1 | 类型:主题研究(原书二手精读 + 个人代码库实证)
主题对象:Martin Fowler, *Refactoring: Improving the Design of Existing Code*(第 2 版,2018)——聚焦两个核心构件:坏味道目录(第 3 章 "Bad Smells in Code")与安全小步重构(行为保持变换 + 测试安全网)。
一、本次精读来源(检索并逐篇读过)
| # | 来源 | 性质 | 本次实际读到的内容 |
|---|---|---|---|
| 1 | bliki: Code Smell(Fowler, 2006 修订) | 原文(Fowler 官网) | 全文。坏味道的定义、可嗅探性、"不必然是问题"、教学用法 |
| 2 | Refactoring 书籍页(refactoring.com/martinfowler.com) | 原文(作者自述) | 全文。重构定义、"too small to be worth doing"、全书结构 |
| 3 | The Second Edition of "Refactoring"(Fowler, 2018) | 原文(作者写作备忘) | 全文含各 memo。第二版改动规模、68/10/17 数字、JavaScript 示例、web-first、用 git commit 序列管理重构 |
| 4 | Catalog of Refactorings(refactoring.com/catalog) | 原文(官方目录) | 全部条目与标签。本次抓取逐一清点为 66 个重构条目,含别名与 20 个分类标签 |
| 5 | Refactoring Chapter 3 — Bad Smells in Code(Rafael Melo, Medium, 2020) | 二手(第 3 章摘录式总结) | 全文。各坏味道的原书要点摘录与多个原句 |
| 6 | bliki: Self Testing Code(Fowler, 2014 修订) | 原文(Fowler 官网) | 全文。自测试代码 = 重构的安全网,"bug detector" 比喻 |
检索补充(未逐页精读,仅搜索结果确认):
- O'Reilly 第 3 章目录(搜索结果):第 2 版第 3 章共 24 个坏味道(Mysterious Name → Comments)。
- Workflows of Refactoring infodeck(Fowler, 2014):本次抓取只读到目录结构(Two Hats / TDD Refactoring / Litter-Pickup Refactoring / Comprehension Refactoring / Preparatory Refactoring / Planned Refactoring / Long Term Refactoring),正文幻灯片未读到。
- Kent Beck 名言 *"for each desired change, make the change easy (warning: this may be hard), then make the easy change"*:来自搜索结果片段,未直接读到原页正文,引用时以此为准。
个人代码库:D:/CodexData/nightshift(用户自己的夜间知识工作流系统;nightshift_state.py 418 行、nightshift_scheduler.py 315 行、test_nightshift.py 487 行,共约 1220 行 Python)。
二、七维提取
1. 核心问题
两件事,且互为因果:
- 何时重构——面对一段"感觉不对"的代码,如何在几秒到几分钟内低成本地判断"这里值得动手"?逐行做设计评审太贵,不判断又会让技术债滚雪球。
- 如何安全地重构——"改善内部结构而不改变外部行为"说起来一句话,做起来凭什么不改坏?大规模重写风险人尽皆知,那风险是如何被拆解掉的?
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."
- 测试是安全小步的前提,不是可选附件。Self Testing Code:
"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."
没有测试的旧代码库陷入"恐惧螺旋":怕改坏 → 不敢重构 → 技术债增加 → 更怕改。有测试则进入"良性螺旋":小清理可以安全做 → 代码保持健康 → 加功能越来越快。
- 两顶帽子(Two Hats):加功能与重构是两种不同的工作模式,同一时段只戴一顶;重构的时段里唯一的度量是"代码结构变好了、测试仍然全绿"。 Fowler 还区分了多种重构时机(预备性、理解性、捡垃圾式、计划性、长期性)——见 Workflows of Refactoring 目录。Kent Beck 的"先让改动变容易,再做容易的改动"(引述见来源一节说明)是预备性重构的口号。
- 坏味道目录还是教学工具。Code Smell bliki 记录了一个实践:团队负责人指定"本周坏味道",全员去代码里找出来交给资深成员判断——
"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. 方法(书的技术路线)
- 开场示例:一个完整的小型重构全程,展示"闻味道 → 小步变换 → 跑测试"的循环。第二版把中间一阶段重写时,Fowler 自己发现"照着 Split Phase 的机械步骤做明显更容易"——这是他对"手法目录的 mechanicals 大多数时候好用"的自我验证。
- 原则章:重构的定义、两顶帽子、为何小步。
- 坏味道章(第 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 章摘录
- 测试章:自测试代码。
- 手法目录(主体):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. 数据与证据
文本侧(本次逐一读到):
- 定义与小步主张:书籍页整段引文(见上文主要主张)。
- 数字:第二版"目录 68 个重构、其中 10 个未保留自一版、新增 17 个";"印刷版 63 个";页数 416(较一版 412+ 略厚但换薄纸);示例语言 Java → JavaScript。
- 坏味道清单:第 2 版第 3 章 24 个——Mysterious Name, Duplicated Code, Long Function, Long Parameter List, Global Data, Mutable Data, Divergent Change, Shotgun Surgery, Feature Envy, Data Clumps, Primitive Obsession, Repeated Switches, Loops, Lazy Element, Speculative Generality, Temporary Field, Message Chains, Middle Man, Insider Trading, Large Class, Alternative Classes with Different Interfaces, Data Class, Refused Bequest, Comments(经 O'Reilly 目录搜索结果 + Medium 摘要交叉确认;原书章节全文付费未直接读)。
- 目录清点:refactoring.com/catalog 本次抓取 66 个条目(Extract Function / Extract Class / Replace Temp with Query / Split Phase / Replace Conditional with Polymorphism / Replace Magic Literal / Remove Dead Code / Encapsulate Variable 等,均有别名映射,如 Extract Method、Replace Magic Number with Symbolic Constant)。
- 第 3 章摘录中的判据类原句(Medium 摘要):
- Data Clumps:"A good test is to consider deleting one of the data values. If you did this, would the others make any sense? If they don't, it's a sure sign that you have an object that's dying to be born."
- Comments:"When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous."(注释只该解释"为什么","A good time to use a comment is when you don't know what to do")
- Primitive Obsession:"we see calculations that treat monetary amounts as plain numbers, or calculations of physical quantities that ignore units"
- 重构过程的实证方法(Fowler 写第二版时本人流程,refactoring-2nd-ed memo):"I like to keep all my code examples 'live', so that I can change the code, run tests to ensure it still works"——把每个重构序列存成独立 git 仓库的一串 commit,书里的代码片段按 commit ref 自动导入。这等于作者本人每天在用"小步 + 测试 + 版本控制"三件套。
代码侧(个人代码库,本次通读并实测):
- 安全网实测命令与结果:
python -m unittest discover -s D:/CodexData/nightshift/scripts -p test_nightshift.py -v→ "Ran 38 tests in 1.483s / OK"。487 行测试、3 个测试类(Maintenance / Scheduler / SchedulerIdentity),用TemporaryDirectory隔离状态(test_nightshift.py:37-48)。 - 坏味道逐条定位(行号本次逐一核对,详见第三节)。
5. 结论
- 坏味道目录的价值不在"评分",而在把设计判断变成可传播的嗅觉训练:新手能闻出来("it's easy for inexperienced people to spot them"),资深者来判断与开方;"每周一个坏味道"是已被作者复述两次的团队教学法。
- 手法目录的价值在把"重构"从勇气问题变成工序问题:每个手法都有机械步骤,跟着走大多数时候能到终点(Fowler 在 Split Phase 重写示例时亲身验证)。
- 两者由测试缝合:坏味道告诉你去哪,手法告诉你怎么走,测试保证每一步没走坏,小步保证每一步可回退。缺任何一件,另外三件都会失效——这正是"assume that any non-trivial code without tests is broken"的原因。
6. 局限
- 原书章节未直接读到:第 3 章全文与各手法的 mechanics 正文在付费墙后,本笔记的坏味道清单与引文经二手摘要 + 搜索结果双重来源确认,已逐处标注;如需逐字校对须回到原书。
- Kent Beck "make the change easy" 名言与 Workflows of Refactoring 各幻灯片正文本次未直接读到(infodeck 只返回目录),相关引用强度打折。
- 坏味道目录本质是启发式而非准则,作者自己承认"Some long methods are just fine";它无法回答"停"的那一半问题(何时停止重构),第 3 章开篇也只承诺"指出方向"而不给精确判据。
- 个人代码库样本偏窄:nightshift 约 1.2k 行、脚本型、非 git 仓库(本次核实
git -C D:/CodexData/nightshift log报 "not a git repository"),所以 Divergent Change / Shotgun Surgery 这类基于变更历史的味道只能从代码结构推断,且"小步 + commit 回退"链条里缺了 commit 这一环——安全网只剩测试单腿。 - 代码库规模小,第 3 章里 Temporary Field、Refused Bequest、Message Chains 等面向对象大库的味道在样本中未观察到,映射表覆盖不全。
- 测试全绿 ≠ 重构零风险:38 个用例未覆盖的分支(如 kb manifest 出错的路径)重构时仍需先补测试再动手。
7. 对我的启发(结合 nightshift 的可执行项)
(a)nightshift 坏味道盘点(本次通读结果,行号已核对)
| 坏味道(第 3 章名) | 位置 | 证据 | 对应手法(catalog 条目) |
|---|---|---|---|
| Long Function | nightshift_state.py:300-388 | execute() 共 89 行,单函数内分发 scan/verify/commit/fail 四个命令,且每个命令里再按 research/knowledge/repo-audit/kb 四种 kind 分支 | Extract Function(按命令各抽一个 scan_command 等) |
| Divergent Change | 同上 | 每新增一种 kind 或一个命令都要改同一个函数——两种变化原因挤在一起 | 先 Extract Function 分命令,再考虑按 kind 收敛特判 |
| Duplicated Code | nightshift_state.py:55-61, 70-75 | locked() 内 Windows(msvcrt)/POSIX(fcntl) 的加锁与解锁两段平台分支重复出现两次 | Extract Function(_acquire_lock / _release_lock) |
| Duplicated Code(近似) | nightshift_state.py:334, 353 | entries = manifest.get("entries", []) if manifest else ... 推导写了两遍且略有差异(334 行多一个 entry_json 分支) | 先统一为一处,差异显式化 |
| Global Data | nightshift_state.py:16 | KB = Path("G:/我的云端硬盘/...") 硬编码外部路径;测试被迫在 test_nightshift.py:40-41 用 self.m.KB = ... 覆盖模块属性 | Encapsulate Variable(收进配置/参数,测试不再"扒开模块改常量") |
| Comments(气味版) | nightshift_scheduler.py:39-41 | route() 里用三行注释记录"2026-11-03 按用户要求 repo-audit 移出轮换"——变更日志写进了代码;时间片阈值(:25/:40)也随之漂移 | 把时间片表提为数据(Replace Magic Literal + 配置外置),注释进 CHANGELOG |
| Mutable Data | nightshift_state.py:241-248, 261-297 | mark_failure / mark_complete 原地改共享 state 字典并多次 save_state;mark_complete 失败回滚要手工恢复 state[k]["completed"] = False(:295) | 最小步:收敛"状态写点"到单一出口;暂不上不可变结构 |
| Mysterious Name | nightshift_state.py:103, 111 | inside() / nonempty() 名字看不出"校验路径在允许根内 / 校验非空文件" | Rename Function |
(b)一次安全小步的完整演练(以 execute() 为例,下一步就能做)
- 戴重构帽之前先确认安全网:跑
python -m unittest discover -s D:/CodexData/nightshift/scripts -p test_nightshift.py -v(本次实测 38 tests / 1.483s / OK)。覆盖不到的分支(kb 错误路径)先补测试。 - 第一个小步:只抽 scan 分支——把
nightshift_state.py:302-329的prepare(kind)+with locked()块原样搬进def scan_command(args):,不改一行逻辑。这符合"too small to be worth doing"标准:单看这一步"不值得",但它把 89 行函数砍掉三分之一且零风险。 - 验证 + 落盘:跑测试(应 38 绿)→ 这一步的关键。nightshift 目前不是 git 仓库,回退只能靠手工;把
git init+ 每小步一 commit 纳入流程,是把 Fowler 写书流程("change the code, run tests" + commit 序列)补齐的最后一块。 - 重复:verify_command → commit_command → fail_command 各抽一次,每步之间测试必须全绿;期间任何一步变红,回退的只是"一小步"。
- 最后才动结构:四个命令各自成函数后,kind 特判是否要收敛成查找表,此时才看得清——这印证了"重构会训练代码进入新形状"(refactoring-2nd-ed 原句)。
(c)三个工作流层面的启发
- "每周一个坏味道"个人版:nightshift 的 backlog 已经按条目轮换,可以用同样节奏给代码安排"smell of the week"——本周只清 Duplicated Code,下周只清 Mysterious Name,比"大重构周末"可持续。
- 加功能前先问"能否让改动变容易":下次要往
route()加新时间片或新 kind 时,先做五分钟"捡垃圾式重构"(Litter-Pickup Refactoring)——顺手把时间片表提为数据——再加功能(两顶帽子,各戴各的时段)。 - 别把坏味道当 KPI:
route()的三行注释其实是好注释(记录"为什么"),该清理的是它注释的事实(配置漂移),不是注释本身——"first try to refactor the code so that any comment becomes superfluous"的正确用法。
附:重要原文引述索引(便于回查)
- "A code smell is a surface indication that usually corresponds to a deeper problem in the system." — Code Smell
- "smells don't always indicate a problem... they are often an indicator of a problem rather than the problem themselves." — 同上
- "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 书籍页
- "of those 68 refactorings, all but 10 are still present, and I've added 17 new ones" — The Second Edition of "Refactoring"
- "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)
- "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)
- "When you feel the need to write a comment, first try to refactor the code so that any comment becomes superfluous." — 同上
- "you simultaneously build a bug detector that's able to detect any faults inside the system" — Self Testing Code
- "Our attitude is to assume that any non-trivial code without tests is broken." — 同上
- "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" — 同上
附:本次执行记录
- 网页精读:CodeSmell.html / books/refactoring.html / articles/refactoring-2nd-ed.html / refactoring.com/catalog / Medium 第 3 章摘要 / SelfTestingCode.html(均全文抓取);workflowsOfRefactoring infodeck 仅目录结构。
- 代码通读:nightshift_state.py(418 行全文)、nightshift_scheduler.py(315 行全文)、test_nightshift.py(前 80 行 + 函数清单)、workspace/scripts/html2txt.py(39 行)。
- 实测命令:
python -m unittest discover -s "D:/CodexData/nightshift/scripts" -p "test_nightshift.py" -v→ Ran 38 tests in 1.483s, OK;git -C D:/CodexData/nightshift log→ "not a git repository"。 - 本笔记为 knowledge-h6n221ha-1 唯一新建文件,未改动其他条目笔记、matrix.csv、backlog 或 nightly-log.md。