应用程序开发中的遗留代码 — 定义、风险与应对策略

作者: IT Sectr 发布日期: 2026-07-27 阅读时间: 7 分钟

遗留代码 — 不仅仅是旧代码。它是一个能够为企业赚钱、但阻碍发展的运行系统。在移动应用开发中,遗留代码可能用 Objective-C 编写,使用过时的库或架构模式。根据 CAST Software(2024)的报告,企业项目中一行代码的平均年龄超过14年。遗留代码工作策略决定了它是会成为绊脚石还是保持为可控资产。

要点

  • 遗留代码 — 在生产环境中运行但使用过时技术或方法的代码
  • 遗留代码维护 需要理解决策历史并进行谨慎的重构
  • 迁移策略 — 通过 Strangler Fig 模式在不停止产品的情况下逐步替换模块
  • 遗留代码测试 — 特征测试在重构前记录当前行为
  • 代码年龄 本身不是问题 — 问题在于缺乏测试和架构视野

什么是应用程序开发中的遗留代码

遗留代码 — 仍在生产环境中运行但已不符合现代质量标准代码或系统。遗留代码可能使用过时的语言编写(例如用 Objective-C 而不是 Swift),使用不受支持的库或早已被视为反模式的架构模式。

遗留代码的关键特征是缺乏测试。根据 Michael Feathers(2004)的定义,遗留代码就是没有测试的代码。如果行为无法安全更改,无论年龄如何,系统都处于遗留状态。没有单元测试的新代码 — 就是第一天的遗留代码。

遗留代码未必是坏事。一个用 Java 8 精心设计的系统可能比用 Kotlin 协程编写的混乱代码更可靠、更易理解。代码年龄 不是质量指标 — 重要的是系统是否易于更改和扩展。

为什么遗留代码是正常的

每个成功的系统 最终都会变成遗留代码。这是一个自然过程:技术的发展速度超过了代码重写的速度。5年前用 Swift 2 编写的应用今已成遗留代码,尽管在创建时它是先进的。

遗留代码的商业价值经常被低估。系统稳定运行、处理交易、存储数据 — 重写需要承担风险。根据 Standish Group(2024)的数据,35%的完全重写项目以失败告终。从经济角度讲,合理做法不是摆脱遗留代码,而是学会与之共存。

最佳策略 — 逐步迁移、将旧代码封装在新接口之后并进行自动化测试。遗留代码只有当其无法以可预测的成本进行更改时才成为问题。

遗留系统的主要标志

缺乏自动测试 — 主要指标。如果修改一行代码后,开发人员无法运行测试并确保没有破坏任何东西 — 您面对的就是遗留代码。附加标志:部署过程耗时数小时并需要手动操作。

文档与代码不符 — 另一个标志。架构图表过时,注释描述的行为已经改变。新开发人员上手时间 超过一个月 — 系统高复杂度和低可维护性的标志。

其他标志:没有明确边界的单体架构、手动测试作为主要验证方法、长时间CI流水线(超过30分钟)、使用没有最新版本的库以及无法在不破坏相邻模块的情况下更新依赖。

“脆弱代码”现象 — 一个地方的更改破坏了其他三个地方。这是紧耦合(tight coupling)的结果,即模块之间相互知道太多。耦合度越高,系统进入遗留代码类别的速度就越快。

使用陈旧代码的风险

速度下降 — 主要风险。添加一个简单功能需要数小时研究代码和数天测试。根据 Stripe(2024)的报告,开发人员花费33%的时间来克服技术债务,这与项目中遗留模块的存在直接相关。

专业知识流失 — 原始代码的作者离开公司,文档不完整。新开发人员害怕触碰无法理解的模块,导致 “冻结代码” 效应:模块不发展但继续运行。此类系统的 Bus factor 极低。

安全性 — 过时的库包含已知漏洞。在Java项目中使用 OpenSSL 1.0.2 或旧版 Jackson 是直接通向安全事故的道路,可能使企业损失声誉和客户。

团队士气低落 — 在没有改进策略的情况下处理遗留代码会降低开发人员的满意度。团队不再为产品感到自豪,人员流动增加,进一步拖慢了系统的开发。

遗留代码重构策略

特征测试 — 对遗留代码进行任何更改前的第一步。在已知输入数据上运行代码并记录预期输出。这些测试将当前行为作为规范记录下来。Golden master 测试 — 输出数据与参考文件进行比较的变体。

Seam 分析 — 寻找可以在不改变行为的情况下断开耦合的点。Michael Feathers 区分了几种 seam 类型:preprocessor seam、object seam、link seam。Object seam — 最常见:通过接口用测试存根替换真实对象。

抽芽方法(Sprout method)和抽芽类(Sprout class) — 在旧代码旁边而非内部添加新代码的技术。不修改现有方法,而是创建具有所需逻辑的新方法并从旧方法中调用它。这样可将破坏工作代码的风险降至最低。

示例:在遗留代码中添加日志记录

groovy
class LegacyPaymentProcessor {
    def process(payment) {
        // 不应触碰的200行遗留代码
        logPayment(payment) // sprout method
    }
    def logPayment(payment) {
        // 在遗留代码旁添加的新代码
    }
}

迁移到现代技术栈

Strangler Fig 模式 — 推荐的遗留代码迁移方法。新模块并行创建,流量逐步从旧模块切换到新模块。旧模块在停止接收请求时自然 “消亡”。该模式最大程度降低了风险,并在出现问题时允许回滚。

通过抽象分支(Branch by Abstraction) — 在旧实现和新实现之上创建抽象的技术。客户端代码切换到抽象层,旧实现逐步被新实现取代。示例:通过网络服务协议将网络层从 AFNetworking 替换为 Alamofire。

逐步迁移 — 将过渡分解为小步骤:封装旧模块 → 编写测试 → 创建新模块 → 并行运行 → 删除旧模块。每一步都以稳定的系统状态结束,从而可以随时部署更改。

常见问题解答

是否需要完全重写遗留代码?

完全重写是最危险的选择。只有25%的 Big Rewrite 项目能按时成功完成。最好采用 Strangler Fig 模式:在不停止产品的情况下逐步替换模块。每次迭代都能带来商业价值,风险随着时间的推移而分散。

如何在没有测试的情况下开始重构遗留代码?

从特征测试开始:在已知数据上运行模块,记录结果。Golden master 测试 — 记录行为的简单方法。每次触及一行代码时就添加测试。6个月后,你将拥有一个防止回归的框架。

什么时候不碰遗留代码更划算?

如果系统稳定、不需要频繁更改且不影响其他模块的开发速度 — 就让它去吧。“If it ain’t broken, don’t fix it” — 对于变更频率低的隔离遗留模块来说,这是一种合理的方法。只有在需要进行业务更改时才碰代码。

如何在遗留项目中更新依赖?

使用 语义化版本控制 并逐步更新:patch → minor → major。为每个库编写兼容性测试。Dependabot 或 Renovate 可自动创建更新 PR。如果库已弃用 — 计划通过抽象进行替换。

遗留代码与技术债务有何不同?

技术债务 — 评估延期改进成本的比喻。遗留代码 — 已经过时的具体系统或代码。技术债务可以在一个月内积累,遗留代码需要时间。并非所有技术债务都会变成遗留代码,但所有遗留代码都包含技术债务。

总结

  • 遗留代码 — 无论年龄如何,没有测试的代码。没有测试覆盖率的新代码 — 第一天的遗留代码
  • 代码年龄 — 不是问题。问题在于高耦合度、缺乏测试和文档
  • 特征测试 — 对遗留模块进行任何更改以记录行为前的第一步
  • Strangler Fig 模式 — 逐步替换模块的安全迁移策略
  • 抽芽方法 — 在旧代码旁边添加新代码而不破坏它的技术
  • 35%的完全重写 以失败告终 — 逐步迁移比 Big Rewrite 更安全
  • 隔离的遗留代码 变更频率低,最好不去碰它

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读