引言:为何要在 Swift 中重构遗留代码?
处理遗留代码是 iOS/macOS 开发者工作中不可避免的一部分。使用早期版本的 Swift(2.x、3.x)甚至 Objective-C 编写的项目通常包含庞大的类、重复的逻辑以及缺乏模块化的问题。如果不进行重构,此类代码将成为 Bug 的源头,并拖慢新功能的添加速度。
在本文中,我们将详细拆解在 Swift 中重构遗留代码的实用策略。你将学会如何在不断现有功能的前提下安全地改进架构。所有示例均适用于 iOS 和 macOS 应用程序。
1. 分析与准备:从何入手?
在修改代码之前,必须了解其当前状态。未经分析就动手,很可能破坏正在运行的功能。
1.1. 测试覆盖率(表征测试)
第一步是编写表征测试。这些测试会捕获代码当前的行为,即使该行为看起来有误。只有在此之后,你才能安全地进行修改。
// Example of a legacy function: UserManager.swiftclass UserManager { func fetchUser(by id: Int) -> String { // Outdated implementation with force unwrap let dict = ["id": id, "name": "John"] as [String : Any] return dict["name"] as! String }}
// Characterization testimport XCTestclass UserManagerTests: XCTestCase { func testFetchUserReturnsName() { let manager = UserManager() let result = manager.fetchUser(by: 1) XCTAssertEqual(result, "John") }}1.2. “不良实践”清单
列出代码中存在的问题模式:
- 强制解包(
!)——崩溃的主要源头。 - 全局单例——增加了测试难度。
- 魔法数字和字符串——降低了可读性。
- 长方法(超过 50 行)——违反了单一职责原则。
- 缺乏类型(使用
Any)。
2. 逐步重构:从简到繁
重构遗留代码最好以小步骤进行。每次修改都应可逆,并伴有测试。
2.1. 消除强制解包
将 ! 替换为安全的结构:guard let 或 if let。
// Before refactoringclass NetworkService { func loadData() -> Data { let url = URL(string: "https://api.example.com")! return try! Data(contentsOf: url) }}
// After refactoringclass NetworkService { enum NetworkError: Error { case invalidURL case dataLoadingFailed } func loadData() throws -> Data { guard let url = URL(string: "https://api.example.com") else { throw NetworkError.invalidURL } do { return try Data(contentsOf: url) } catch { throw NetworkError.dataLoadingFailed } }}2.2. 分解“上帝”类
遗留代码中的类常常违反单一职责原则。将它们拆解为更小的实体。
// Problem: the class does everythingclass ProfileViewController: UIViewController { func fetchProfile() { /* network request */ } func parseJSON() { /* parsing */ } func updateUI() { /* UI update */ } func saveToCoreData() { /* saving */ }}
// Solution: separation into modulesprotocol ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void)}
class ProfileNetworkRepository: ProfileRepository { func fetchProfile(completion: @escaping (Result<Profile, Error>) -> Void) { // Network logic