引言:为什么对 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)
}
}
这个测试可能并不完美,但它能记录当前状态。重构后,测试应该仍然通过。