Refactor
解决什么问题:让代码结构持续跟随业务变化,不让结构变成业务演化的阻力——避免"修一个 bug 改 50 个文件" / "加一个 feature 拖 3 周",转而把重构当作持续的活动而不是一次性的大工程。
它是什么
重构(Refactor) 是在不改变外部行为的前提下,改变代码内部结构的工程活动——这是 Martin Fowler 在《Refactoring》(1999/2018)中给出的经典定义:
- 不改变外部行为——重构不是加 feature,不是修 bug
- 改变内部结构——让代码更清晰、更模块化、更易理解、更易扩展
- 小步快跑——一次重构一个小的改动,立刻有测试覆盖
IDDD Vernon Ch 11-14 把重构扩展到领域模型层——Aggregate 重构、Entity 行为重新划分、Value Object 重新识别——这是 DDD 层级的重构。 "Continuous refactoring of the domain model is essential to maintaining a healthy codebase"——领域模型必须随业务持续演化。
SE@G Ch 22 "Large-Scale Changes" 给出了重构的工程模板: "Large-scale changes should be approached as a series of small, atomic, reviewable changes—not as a single big-bang commit"——大规模重构必须是小步的、原子化的、可 review 的。
控制论 Ch 18 §18.3 自适应律(Adaptive Control): 重构 = 控制律的结构性调整——参数调优是自适应(小变),重构是结构演化(大变)。两者都是 B7 演化的具体形态。
V2 视角(V2.7 B7 段 5):重构 = B7 演化的战术工具——业务变了,代码结构必须跟着变;不重构 = 结构变成业务演化的阻力。
关键属性
| 属性 | 含义 | 反模式 |
|---|---|---|
| 不改变行为 | 重构前后外部行为完全一致 | 把"修 bug"当作重构 |
| 小步进行 | 一次只改一个维度 | 一次性大重构 |
| 测试覆盖 | 重构前后测试都通过 | 没有测试就重构 = 赌博 |
| 持续进行 | 不是一次性活动 | "等有空再重构" = 永远不重构 |
| 可回滚 | 每次重构可独立回滚 | 一次性 commit 改 100 个文件 |
| 动机驱动 | 有明确"为什么重构" | "代码不好看"不够,必须有具体痛点 |
| 与迭代分离 | 重构和加 feature 分开 commit | "顺便重构一下" = 风险叠加 |
| DDD 维度 | 包含领域模型的结构调整 | 只重构代码不重构模型 = 治标 |
怎么用
场景 1:识别"该重构了"
4 个标志性信号:
1. 修一个 bug 要改 > 5 个文件
2. 加一个 feature 要 > 3 天
3. 测试覆盖开始下降
4. 团队开始说"别碰这块代码"
更精确的判断:
- Cyclomatic complexity 上升
- 类/函数越来越大
- 模块边界越来越模糊
- 命名和实际行为越来越不一致场景 2:重构的标准流程(Fowler 模板)
1. 准备测试(保证现有行为被测试覆盖)
↓
2. 识别坏味道(Long Method / God Class / Feature Envy 等)
↓
3. 选择重构手法(Extract Method / Move Method / Rename 等)
↓
4. 小步应用(一次只做一个手法)
↓
5. 运行测试(保证没坏)
↓
6. commit(独立 commit,message 说明动机)
↓
7. 重复(继续下一个坏味道)原则:永远不要在重构时加 feature——两者必须分开 commit。
场景 3:DDD 层级的重构(IDDD Ch 11-14)
聚合重构:
- 聚合边界重新划分(业务变了,原聚合不再合适)
- 聚合根替换(原来 Entity 不应该是聚合根)
- 不变量重新识别(业务规则变了)
实体 / 值对象重构:
- 值对象升级为实体(业务识别到身份)
- 实体降级为值对象(业务认识到可替换)
- 行为重新分配(哪个行为属于哪个对象)
领域服务重构:
- 业务逻辑从 Entity 移到 Domain Service
- 业务逻辑从 Application Service 移到 DomainVernon 反复强调:领域模型必须随业务持续重构——业务变了,模型不变 = 模型失真。
场景 4:大规模重构(SE@G Ch 22 模板)
场景:跨 100 个服务的接口签名修改
错误做法:一次 commit 改完所有 100 个服务 → 100 个团队同时升级 → 协调灾难
正确做法(SE@G Ch 22):
1. 工具辅助:LSC 工具自动生成原子化变更
2. 渐进发布:每个服务独立 PR,独立 review
3. 灰度上线:新旧接口并存,逐步切换
4. 旧接口 deprecation:标记 deprecated → 监控使用 → 给迁移路径 → 删除关键:大规模重构 = 多个小重构的并行——而不是一个 big-bang 重构。
场景 5:重构的时机选择
最差时机:发版前一周(测试压力大)
差时机:项目交付前
好时机:每次需求开发"顺便"做(10-20% 时间投入)
最佳时机:每个迭代周期(Sprint)预留 10-20% 时间做专门重构原则:重构 = 持续活动,不是项目——和迭代混在一起做 = 风险叠加;单独做但持续 = 健康。
常见误区
- "重构 = 重写" ——错。重构不改变外部行为;重写可能改变行为。两者完全不同
- "重构不需要测试" ——错(致命)。没有测试覆盖就重构 = 赌博——重构的前提是测试
- "重构 = 加 feature" ——错。重构不改行为;加 feature 改行为。混在一起 = 风险叠加,难回滚
- "一次性大重构" ——错(SE@G Ch 22 反对)。大规模重构必须原子化、渐进式、可 review
- "重构是架构师的事" ——错。每个工程师都该重构自己负责的代码;架构师负责跨团队的大规模重构
- "代码能跑就别动" ——错。"能跑" ≠ "能演化"——业务在演化,代码结构不演化 = 代码变成业务演化的阻力
关键洞察
重构 = 不改变行为前提下改变代码结构 + 持续进行。 重构不是一次性项目,是持续活动——和迭代同步进行,每个 Sprint 预留 10-20% 时间。 控制论 Ch 18 自适应律(参数变)→ 重构(结构变);IDDD Ch 11-14 领域模型重构;SE@G Ch 22 大规模重构模板——三者共同构成 B7 演化的战术工具集。 判断标准:代码变成业务演化的阻力时(修 bug 难、加 feature 慢、测试覆盖降)= 必须重构。
相关链接
- Evolvability——重构的长期目标
- Large-Scale Changes——大规模重构模板
- Evolution vs Iteration——重构的分类
- [V3 涌现之时] · 演化——重构的理论基础
- IDDD Ch 11-14 领域模型重构——DDD 层级的重构
- SE@G Ch 22 Large-Scale Changes——大规模重构工程模板
- 工程控制论 Ch 18 §18.3——自适应律的对位