Swift遗留代码重构:从A到Z

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么对 Swift 遗留代码进行重构是必要的

每个 iOS/macOS 开发者迟早都会遇到遗留代码。这可能是一个用 Objective-C 编写并部分迁移到 Swift 的项目,或者是一个在 Swift 2.0 时代使用 Massive View Controller 创建的应用程序。这样的代码通常能运行,但维护起来却如同噩梦:任何修改都需要数小时的测试,而添加新功能往往会破坏旧功能。

对 Swift 遗留代码进行重构不仅仅是“梳理”代码。这是一个战略性的过程,它能使应用程序更易于测试、扩展和高效。在本文中,我们将通过 Swift 示例,详细讲解如何正确地进行重构,以避免破坏生产环境并获得可运行的结果。

1. 诊断:如何判断遗留代码需要重构

在修改代码之前,需要明确哪些部分需要更改。Swift 遗留代码的主要特征包括:

  • Massive View Controller (MVC 反模式) — 文件超过 500 行,逻辑、UI 和网络请求混杂在同一个类中。
  • 强耦合 (tight coupling) — 类之间直接相互依赖,没有使用协议和依赖注入。
  • 缺乏测试 — 如果你害怕修改一行代码,因为不知道会破坏什么。
  • 全局变量和单例 — 在整个项目中滥用 UserDefaults.standard 或 NotificationCenter.default。
  • 强制解包 (Force unwrapping) — 到处使用 ! 而不是安全地提取可选值。

1.1. 分析 Swift 遗留代码的工具

为了客观评估代码状态,请使用以下工具:

  • SwiftLint — 静态分析器,用于检测代码风格违规。
  • Xcode Instruments — 用于查找内存泄漏和性能问题。
  • Code coverage — 显示代码被测试覆盖的比例(在遗留项目中通常为 0-10%)。

一个需要重构的典型遗留代码示例:

class LegacyViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!
    var data: [String] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 逻辑和 UI 混合
        let url = URL(string: "https://api.example.com/data")!
        let data = try! Data(contentsOf: url)
        let json = try! JSONSerialization.jsonObject(with: data) as! [[String: Any]]
        for item in json {
            data.append(item["name"] as! String)
        }
        label.text = data.first
    }
}

这里存在多个问题:强制解包、在主线程中同步执行网络请求、逻辑与表示层混合。

2. Swift 遗留代码的重构策略:分步计划

没有策略的重构是通往灾难的道路。请采用“工兵排雷”的方法:先确保安全区域,再修改代码。

2.1. 第一步:创建由测试组成的“安全网”

在修改任何内容之前,先为现有代码编写测试。使用 Characterization Tests — 这些测试用于记录系统的当前行为。即使代码看起来很糟糕,测试也能表明你没有破坏逻辑。

import XCTest
@testable import LegacyApp

class LegacyViewControllerTests: XCTestCase {
    
    func testViewDidLoad_SetsLabelText() {
        let sut = LegacyViewController()
        sut.loadViewIfNeeded()
        // 检查 label 不为 nil 且包含文本
        XCTAssertNotNil(sut.label.text)
    }
}

这个测试可能并不完美,但它能记录当前状态。重构后,测试应该仍然通过。

2.2. 步骤

博客

书籍推荐