跳转到内容

Legacy 代码只要好好重构就行

为什么 Legacy 系统的问题很少只是组件难看,而更多来自未知依赖、缺少测试和隐性的业务知识。

听起来像一个很合理的任务。

“这是 Legacy。我们得找时间好好 Refactor 一下。”

整理一下。 拆几个大组件。 把几个 Service 名字改得漂亮一点。 挪几个文件。 然后团队就能重新变快。

可惜,这通常并不只是 Refactoring。

很多时候,它更像是在开胸状态下做 Reverse Engineering。

因为 Legacy 系统的问题很少只是代码难看。真正棘手的是未知依赖、缺失的测试、隐性的业务知识、长期积累的特殊情况,以及那些已经没人能准确说清楚的需求。

低估这些问题,就等于把 Refactoring 当成大扫除。

但如果没人知道哪一面墙是承重墙,打扫得再干净也没用。

Refactoring 不是美化代码,而是在安全保障下进行可控的改变。

问题往往就出在“简单”上。

“简单 Refactor 一下。”

这句话听起来像是问题已经被理解,剩下的只是执行;仿佛只要给团队一点时间,就能把代码整理得更漂亮。

但 Legacy Code 很少只是“不整洁”。

Legacy Code 更常见的情况是:系统行为仍然在生产环境中被依赖,但这些行为为什么存在,已经没人完全知道。

为什么有这个例外? 为什么这个字段要在三个地方设置? 为什么这个状态不能删除? 为什么这个组件直接调用这个 API? 为什么这个校验还在 Template 里? 为什么同一个状态同时存在于 Store、Form 和 Service?

答案往往不在代码里。

它可能藏在旧 Ticket、被遗忘的 Bug、客户 Workaround、业务特殊情况、数据迁移、历史 Release 事故里,或者只存在于某个已经去了别的团队的人脑子里。

这不是“代码难看”。

这是未知的系统行为。

很多 Legacy 改造项目都从类似的话开始:

“这个系统得跑得更好。”

或者:

“团队必须变快。”

又或者:

“旧界面需要 Refactor。”

问题在于,这些句子表达的是不满,却没有定义一个可以被控制和验证的变化。

“更好”到底是什么意思?

加载更快? 更少 Regression? 更短的 Feature Cycle Time? 更好的可测试性? 更低耦合? 更容易 Onboarding? Release 更安全? 更少手工测试? 更少对修改的恐惧?

如果这些问题不先说清楚,Refactoring 就会变成每个人投射自己期待的容器。

对开发者来说,它意味着偿还技术债。 对 Product Owner 来说,它意味着更少 Bug。 对 Management 来说,它意味着更高速度。 对 QA 来说,它意味着更少手工测试。 对运维来说,它意味着更低的生产风险。

所有人都在说“Refactoring”。

但每个人说的其实不是同一件事。

不满是一种信号,但还不是 Refactoring 策略。

没有测试时,Refactoring 往往变成 Reverse Engineering

Section titled “没有测试时,Refactoring 往往变成 Reverse Engineering”

Refactoring 并不是:

“我改代码,直到它看起来更漂亮。”

Refactoring 的含义是:

“在不改变可观察行为的前提下,改善内部结构。”

要做到这一点,至少得知道两件事。

第一:哪些行为必须保留? 第二:我们怎么确认这些行为还在?

如果这两件事都不清楚,Refactoring 就会变得危险。

没有测试,每次修改都只是一个主张。 没有业务示例,每次简化都只是一个猜测。 不知道边界条件,每次清理都可能变成 Bug。 没有 Monitoring,一些错误甚至要到生产环境才会被发现。

在这种系统里,第一步通常不是“大规模整理”。

第一步是建立安全网。

有时是 Unit Test。 有时是 Integration Test。 有时是 Characterization Test。 有时是 Golden Master Test。 有时是 Logging。 有时是整理业务示例。 有时只是先真正理解系统现在到底在做什么。

这会让人感觉很慢。

但总比勇敢地蒙着眼飞要快。

如果连应该保持什么行为都不知道,Refactoring 就不再是结构优化,而是靠猜测进行 Reverse Engineering。

当然,2000 行的组件确实是问题。

但它通常不是最根本的问题。

一个巨大组件往往只是许多未解决问题集中显现的地方:

  • UI State 和业务 State 混在一起
  • API DTO 直接一路进入 Template
  • 校验逻辑散落在多个位置
  • 副作用发生在 Lifecycle Hook
  • 权限规则没有统一归属
  • Error Handling 是隐式的
  • 数据被局部直接修改
  • 业务逻辑被推到了 UI
  • 特殊情况从未被真正建模

如果只是把文件拆开,问题也会跟着被拆开。

你不再拥有一个巨大组件。

你会得到八个更小的文件,但它们仍然组成同一个没有被理清的系统。

看起来更好。

并不代表真的更好。

大组件往往不是问题本身,而是问题最容易被看见的地方。

好的 Legacy Refactoring,不是从 Rename 开始。

而是从诊断开始。

哪些部分变化最频繁? 哪些部分在业务上最关键? 哪些部分最容易出错? 哪些部分让人害怕? 哪些部分阻碍新 Feature? 哪些部分只是难看,但其实稳定? 哪些行为必须被保留? 哪些部分可以直接替换?

然后还需要一个顺序。

不是每一笔技术债都同样重要。 不是每个难看的地方都值得优先处理。 不是每个 Rewrite 都错误。 也不是每次 Refactoring 都值得做。

一套可用的策略至少要回答四个问题:

  1. 我们要降低什么风险?
  2. 哪些行为需要先被保护?
  3. 下一步要让哪一条边界变清晰?
  4. 我们怎么知道这次改变真的有帮助?

没有这些问题,Refactoring 很容易变成“忙起来”。

有了这些问题,它才开始变成 Modernization。

Legacy 工作不是大扫除,而是受控的风险降低。

决策者也必须为 Refactoring 创造条件

Section titled “决策者也必须为 Refactoring 创造条件”

一个常见错误,是把 Refactoring 当成纯粹的开发任务。

“你们是技术人员,这些就顺手做掉吧。”

这很少奏效。

不是因为开发者在找借口。

而是因为真正的 Legacy Refactoring 需要业务和组织层面的决策。

哪些业务行为是关键的? 哪些历史特殊情况已经可以删除? 哪些 Workaround 仍然有效? 哪些风险可以接受? 哪些区域可以先隔离? 改造期间哪些 Feature 会暂时变慢? 最后我们真正关心哪些 Metric?

这些问题,开发团队无法独自回答。

团队可以展示选项。 可以解释风险。 可以提出技术路径。 可以设计增量步骤。

但如果没有人澄清业务优先级,就不会出现真正的计划。

只会出现压力。

而压力施加在 Legacy 系统上,通常不会产生更好的架构。

它只会产生更多 Shortcut。

Refactoring 不只是开发者的工作,它需要决策、优先级和安全保障。

“团队必须更快”往往是错误诊断

Section titled ““团队必须更快”往往是错误诊断”

如果一个团队在 Legacy 系统里越来越慢,原因并不一定是团队本身。

有时,是系统让一切都变慢。

每次修改都需要太多上下文。 每个 Bugfix 都可能有副作用。 每个测试都靠手工完成。 每个小改动都需要大量协调。 每次 Release 都让人不安。 每个新人都要几个月才能真正产出。

在这种情况下,“做快一点”不是策略。

它相当于要求大家在一条坏掉的路上开得更快。

好的 Modernization 不只会问:

“这个团队为什么交付这么慢?”

它还会问:

“哪些结构性条件,让快速而安全的交付几乎不可能?”

这个问题更让人不舒服。

但通常也更有用。

让团队变快,不是靠施加更多压力,而是让修改本身变得更便宜。

有时,有针对性的 Rewrite 反而更便宜

Section titled “有时,有针对性的 Rewrite 反而更便宜”

“绝不 Rewrite”与“全部重写”一样,都可能成为危险的教条。

当然,Big Bang Rewrite 风险很高。 团队也经常低估旧系统里隐藏的业务知识。 一个完全重写的系统,很少像规划阶段想象的那样干净。

但有些时候,继续做表面修补,反而比有针对性地替换更贵。

如果某个区域的业务已经被理解得很清楚,边界明确,而现有代码几乎不可能补上合理的可测试性,那么做一个小范围 Rewrite,可能比几个月的外科式修补更划算。

真正的区别,不在于 Refactoring 还是 Rewrite。

而在于过程是否受控。

完整的 Big Bang Rewrite 很危险。 但替换一个边界清楚的 Slice,完全可能是合理方案。

Legacy Modernization 一旦变成英雄叙事,就很危险。

“这次我们重新做一遍,而且一定做对。”

听起来很痛快。

直到新系统必须重新实现旧系统里的所有特殊情况。 直到必须长期并行运行。 直到数据迁移比预期复杂得多。 直到 Feature 压力重新回来。 直到旧系统还是下不了线。 直到两个系统都必须维护。

增量式做法没那么戏剧化。

但通常更专业。

在旧系统旁边建立一个新的 Slice。 划出清晰的接口边界。 建立 Routing 或 Integration Point。 受控地切换。 确认可衡量的收益。 然后再做下一个 Slice。

这没有“大重写”那么浪漫。

但 Legacy 系统很少奖励浪漫。

它奖励小而安全的步骤。

真正的问题不是 Refactoring 还是 Rewrite,而是如何用小而可验证的步骤降低风险。

与其说:

“Legacy 代码简单 Refactor 漂亮一点就行。”

不如更准确地说:

“我们首先要理解哪些行为必须被保留、哪些风险正在拖慢我们,以及下一条边界怎样才能被安全地划出来。”

听起来没那么快。

但更诚实。

Legacy 工作不只是 Code Work。 它同时是分析、建立安全网、架构设计和决策工作。

如果要求团队 Refactor,也必须为它提供条件:

  • 用于诊断的时间
  • 能够接触业务知识
  • 对历史特殊情况做出决策
  • 测试或其他安全网
  • 小型 Modernization Slice
  • 清晰的优先级
  • 接受并非所有“看起来难看”的地方都值得最先处理

Refactoring 不是魔法棒。

它更像外科手术。

而对 Legacy 系统动第一刀之前,最好先知道眼前的是皮肤、脂肪,还是动脉。