核心问题

重构的目标是什么?

重构不是为了“代码看起来更漂亮”。

好的重构是为了:

  • 降低认知负担
  • 恢复概念边界
  • 提高局部性
  • 恢复一致性
  • 移除假抽象
  • 让未来变化更自然

核心句:

好重构不是炫技,而是让系统重新变得顺手。

重构不是重写

很多人一说重构,就想推倒重来。

但重写风险很大:

  • 旧系统隐性规则会丢失
  • 新系统长期没有业务价值
  • 新旧系统并行复杂度翻倍
  • 迁移成本被低估
  • 团队容易陷入永远未完成

真正成熟的重构通常是:

在系统还活着的时候,逐步替换它的骨架。

重构前先固定承诺

重构不应该改变外部行为,除非你明确要改变契约。

所以重构前要先补 characterization tests。

例如重构课程访问逻辑前,先固定:

  • 购买后可访问
  • 退款后不可访问
  • 订阅有效可访问
  • 订阅过期不可访问
  • 企业分配可访问
  • 账号封禁后不可访问

测试不是为了证明旧系统完美,而是为了防止重构时无意改变行为。

核心句:

没有证据保护的重构,容易变成重写。

重构的几种目标

1. 改名:恢复概念边界

改名是低成本高价值重构。

例如:

User -> Account

会立刻暴露哪些字段不属于账号:

billingAddress
courseProgress
organizationRole
instructorBio

再比如:

isActive -> canSignIn

会暴露它和订阅有效、课程上架不是一回事。

改名的目的不是美化,而是让系统承认真实边界。

2. 抽规则:减少重复判断

坏状态:

order.status === "paid" && !order.refundedAt

散落在多个地方。

重构:

isPurchaseEligibleForAccess(purchase)

或者:

canGrantCourseAccessFromPurchase(purchase)

这会把隐式规则变成显式名字。

3. 移动代码:提高局部性

如果访问规则在多个模块,移动到:

course-access/policy.ts

如果退款撤销逻辑散落,移动到:

course-access/revocation.ts

移动代码的目标是:

让变化发生在它应该发生的地方。

4. 拆类型:承认真实差异

坏类型:

type UserCourseRelation = {
  accountId: string
  courseId: string
  type: string
  metadata: Record<string, unknown>
}

重构:

Purchase
Enrollment
CourseAssignment
CourseAccessGrant

拆类型不是为了类型多,而是承认不同生命周期和不变量。

5. 删除假抽象

有时候最好的重构是删除抽象。

例如:

UniversalResourceManager

内部全是:

if (resourceType === "course") ...
if (resourceType === "organization") ...
if (resourceType === "payment") ...

这类抽象没有真正统一变化轴。

拆回具体模块,系统反而更清楚。

核心句:

不是所有重构都是抽象,有些重构是去抽象。

小步重构路线

重构最好小步进行。

例如要从混乱 User 模型拆出 AccountOrganizationMembership

1. 给现有 User 增加 Account 语义测试
2. 改名或引入 Account alias
3. 新增 organization_memberships 表
4. 双写 user.companyId 和 organization_memberships
5. 读路径切到 OrganizationMembership
6. 验证一致性
7. 停止写旧字段
8. 删除旧字段

每一步都应该:

  • 可测试
  • 可 review
  • 可回滚或可暂停
  • 不要求后续步骤才能工作

重构时保持系统可运行

避免这种计划:

先改所有模型
再改所有调用方
最后一起修测试

这会让系统长时间处于不可运行状态。

更好的方式:

引入新路径
兼容旧路径
逐步迁移调用方
验证一致性
删除旧路径

也就是 expand-contract。

核心句:

好重构允许你中途停下来,系统仍然可用。

重构和审美指标

重构后应该问:

  1. 名字是否更准确?
  2. 规则是否更集中?
  3. 变化是否更局部?
  4. 错误模型是否更一致?
  5. 新需求是否更有自然落点?
  6. 测试是否更清楚?
  7. 调用者是否知道更少内部细节?
  8. 删除的代码是否比新增的代码更有价值?

如果重构后只是“更高级”,但调用者更难懂,可能不是好重构。

重构的坏味道

1. 无保护重构

没有测试,没有观测,没有回滚。

2. 顺手改需求

重构时偷偷改变外部行为。

如果要改行为,要明确说这是契约变化。

3. 大爆炸重构

一次改太多,review 和回滚都困难。

4. 抽象成瘾

看到重复就抽,没有找变化轴。

5. 品味压人

只说“这样更优雅”,但说不出工程收益。

好的重构应该能解释它降低了什么成本。

重构检查清单

开始重构前问:

  1. 我要改善什么?命名、局部性、一致性、组合性,还是可测试性?
  2. 现有行为有没有测试保护?
  3. 是否会改变外部契约?
  4. 能不能小步提交?
  5. 每一步能不能独立工作?
  6. 是否需要双写、兼容层或 feature flag?
  7. 如何验证迁移完成?
  8. 什么时候删除旧路径?

小结

  1. 重构的目标是降低认知负担、恢复边界和提高可变性。
  2. 重构不是重写。
  3. 重构前先固定系统承诺。
  4. 改名、抽规则、移动代码、拆类型、删除假抽象都是重构。
  5. 好重构小步进行,并保持系统可运行。
  6. 有些重构是抽象,有些重构是去抽象。
  7. 好重构必须能说清它降低了什么未来成本。