Swift遗留代码重构:逐步指南

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

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

开始课程

引言:为何要在 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

博客

书籍推荐