引言:为什么重构Swift遗留代码是一门艺术
处理Swift遗留代码是iOS/macOS开发者面临的最复杂、最负责任的任务之一。遗留项目通常包含“上帝对象”(God objects)、对UIKit的过度依赖、缺乏测试以及混乱的架构。高级重构不仅仅是重命名变量,而是在保持100%功能的前提下对代码库进行系统性改造。在本文中,我们将探讨有助于将混乱转变为可维护架构的技术。
1. 模块化:将单体拆分为组件
迈向规范化代码的第一步是提取独立模块。不要创建一个巨大的目标,而是创建独立的框架或Swift Package Manager模块。
定义模块边界
使用“高内聚,低耦合”原则。示例:提取网络请求层、业务逻辑层和UI层。
// 之前:所有代码都在一个ViewController文件中class LegacyViewController: UIViewController { func fetchData() { // URLSession代码在这里 } func parseData() { // JSON解析代码在这里 }}
// 之后:Network模块public class NetworkService { public func fetchData(completion: @escaping (Result<Data, Error>) -> Void) { // 纯净的网络层 }}通过外观模式进行迁移
不要破坏现有代码:创建一个外观,将调用委托给新模块。逐步替换旧调用。
class LegacyNetworkFacade { private let newService = NetworkService() @objc func oldFetchMethod() { newService.fetchData { result in // 适配旧API } }}2. 依赖注入(DI)替代全局单例
遗留代码常常受困于单例:UserManager.shared、APIClient.shared。这使得测试变得不可能。我们转向依赖注入。
通过初始化器进行手动注入
// 问题class OldViewModel { let api = APIClient.shared let user = UserManager.shared}
// 解决方案protocol APIClientProtocol { /* ... */ }protocol UserManagerProtocol { /* ... */ }
class NewViewModel { private let api: APIClientProtocol private let userManager: UserManagerProtocol init(api: APIClientProtocol, userManager: UserManagerProtocol) { self.api = api self.userManager = userManager }}集成DI容器(Swinject/Resolver)
对于大型项目,使用库。以Resolver为例:
import Resolver
extension Resolver: ResolverRegistering { public static func registerAllServices() { register { NetworkService() as NetworkServiceProtocol } register { UserDefaultsStorage() as StorageProtocol } }}
class RefactoredViewModel { @Injected private var network: NetworkServiceProtocol @Injected private var storage: StorageProtocol}3. 测试:在测试保护下进行重构
没有测试的重构就像玩俄罗斯轮盘赌。我们为旧代码创建“测试框架”(特征测试)。
针对遗留代码的特征测试
将当前行为记录为测试,即使它并不完美。
class LegacyCalculatorTests: XCTestCase { func testLegacyMultiply() { // 记录当前行为 let result = LegacyCalculator().multiply(2, 3) XCTAssertEqual(result, 6) // 即使逻辑奇怪 }}使用黄金主版本进行并行重构
比较旧实现和新实现的输出。使用XCTAssertEqual验证一致性。
func testRefactoredMatchesL