Swift遗留代码重构:从A到Z
\n处理遗留代码(legacy code)是iOS/macOS开发者不可避免的一部分。这些代码通常是用早期版本的Swift甚至Objective-C编写的,缺乏测试,包含“魔法数字”和庞大的UIViewController。在本文中,我们将逐步分析重构此类代码的策略,将其转变为现代、可维护且可测试的项目。
1. 准备与安全:在触碰代码之前
\n重构最重要的规则是:不要破坏正在运行的功能。没有测试,任何更改都如同跳入未知领域。
\n- \n
- 特性测试(Characterization Tests):编写能够固定当前行为的测试。即使代码看起来不正确,也要先记录其工作方式。 \n
- 版本控制系统:提交当前状态的代码。使用功能分支(feature branch)。 \n
- 工具:使用Xcode的
Edit > Refactor(重命名、提取方法),以及代码检查工具(如SwiftLint)来保持统一风格。 \n
// 冻结旧方法行为的示例\nclass LegacyCalculator {\n func calculateTotal(items: [Int]) -> Int {\n // 旧的、难以阅读的逻辑\n var result = 0\n for i in items {\n result += i * 2\n }\n return result + 10\n }\n}\n\n// 固定行为的测试\nfunc testCalculateTotal() {\n let sut = LegacyCalculator()\n let result = sut.calculateTotal(items: [1, 2, 3])\n XCTAssertEqual(result, 22) // (1+2+3)*2 + 10 = 22\n}\n\n\n2. “替罪羊”策略:全局变量和单例
\n遗留代码以其全局状态和单例而臭名昭著。这使得代码高度耦合且不可预测。
\n问题:
\nclass NetworkManager {\n static let shared = NetworkManager()\n var baseURL: String = "http://old.api.com"\n func fetchData() { ... }\n}\n\n解决方案:依赖注入(Dependency Injection)。创建协议并通过初始化器传递依赖。
\nprotocol NetworkService {\n func fetchData()\n}\n\nclass ModernViewModel {\n private let network: NetworkService\n \n init(network: NetworkService) {\n self.network = network\n }\n \n func load() {\n network.fetchData()\n }\n}\n\n这种方法可以轻松地在测试中替换服务,并使代码模块化。
\n\n3. 对抗“臃肿的控制器”(Massive View Controller)
\niOS遗留代码的经典问题:UIViewController负责所有事情——从网络请求到UI格式化。重构通过分离职责(SRP)进行。
步骤1:将业务逻辑提取到单独的模型(ViewModel)中。
\n// 之前在控制器中\nfunc buttonTapped() {\n let data = fetchData()\n let total = data.reduce(0, +)\n label.text = "总和:\\(total)"\n}\n\n// 现在:ViewModel\nclass MyViewModel {\n func calculateTotal(data: [Int]) -> String {\n let total = data.reduce(0, +)\n return "总和:\\(total)"\n }\n}\n\n\n步骤2:将UI配置提取到单独的方法或工厂中。
\n- \n
- 为复杂层次结构创建
ViewBuilder类。 \n - 使用
UICollectionViewDiffableDataSource替代旧的dataSource。 \n
4. 现代化:从Objective-C到Swift和新API
\n遗留代码通常包含Objective-C和Swift的混合。重构时,更新过时的结构很重要:
\n\n- \n
- 用
throws替换NSError:现代Swift使用异常处理错误。 - 放弃
!(强制解包):使用可选绑定(optional binding)或guard let。 - 使用
Codable替代JSONSerialization:简化JSON解析。 - 采用
async/await:用现代并发模型替换基于闭包的回调。
// 旧方式(基于闭包)\nfunc fetchUser(completion: @escaping (User?) -> Void) {\n // ...\n}\n\n// 新方式(async/await)\nfunc fetchUser() async -> User? {\n // ...\n}\n\n\n5. 逐步重构:小步前进
\n不要试图一次性重写所有内容。使用“童子军规则”(每次离开时让营地比来时更干净)。
\n- \n
- 识别“坏味道”:长方法、重复代码、过度耦合。 \n
- 应用重构技术:提取方法、重命名变量、引入参数对象。 \n
- 持续测试:每次更改后运行测试。 \n
结论
\n重构遗留代码不是一次性事件,而是一个持续的过程。从小的安全步骤开始,逐步引入现代实践,您的代码库将变得更健康、更易于维护。记住:完美不是目标,进步才是。