Введение: почему legacy код на Swift требует рефакторинга
Работа с унаследованным (legacy) кодом — неизбежная часть жизни iOS/macOS разработчика. Даже если проект изначально писался на Swift, со временем он обрастает техническим долгом: устаревшие паттерны, отсутствие тестов, монолитная архитектура и смешение Swift с Objective-C. Рефакторинг такого кода не просто улучшает читаемость — он снижает стоимость поддержки, ускоряет добавление новых фич и уменьшает количество багов.
В этой статье мы разберём конкретные техники рефакторинга legacy Swift-кода: от избавления от force unwrapping до миграции с MVC на MVVM. Все примеры можно применить как для iOS, так и для macOS проектов.
1. Оценка и подготовка: с чего начать рефакторинг
Прежде чем менять код, важно понять его текущее состояние. Используйте встроенные инструменты Xcode и метрики.
1.1. Анализ предупреждений и ошибок
Откройте проект в Xcode и перейдите в Report Navigator (Cmd+9). Обратите внимание на:
- Yellow warnings — устаревшие API (deprecated), неиспользуемые переменные.
- Red errors — синтаксические ошибки, которые могут быть следствием неполной миграции Swift.
- SwiftLint/SwiftFormat — подключите линтер, чтобы автоматически выявить нарушения code style.
1.2. Поиск "красных флагов" в коде
Пробегитесь по проекту с помощью поиска (Cmd+Shift+F) по следующим паттернам:
!— force unwrapping (опциональные значения, которые крашат приложение).as!— force casting (приведение типов без проверки).try!— игнорирование ошибок.NSObject— наследование от NSObject без необходимости (чистый Swift этого не требует).#selector— строковые селекторы, которые лучше заменить на#selectorс типом.
// Плохо (legacy)
let name = user.name! // force unwrap — краш при nil
// Хорошо (рефакторинг)
guard let name = user.name else {
return
}
1.3. Написание тестов перед изменениями
Рефакторинг без тестов — это риск. Если тестов нет, начните с их добавления для критических модулей. Используйте XCTest для unit-тестов и XCUITest для UI.
Пример простого теста для legacy функции:
class LegacyCalculatorTests: XCTestCase {
func testCalculateTotal() {
let calculator = LegacyCalculator()
let result = calculator.calculateTotal(items: [10, 20, 30])
XCTAssertEqual(result, 60)
}
}
2. Основные техники рефакторинга Swift-кода
Перейдём к практическим шагам, которые можно применить прямо сейчас.
2.1. Замена force unwrapping на безопасные паттерны
Самая частая проблема в legacy коде — повсеместное использование !. Заменяйте их на guard let, if let или ?? (nil-coalescing).
// До рефакторинга
func getUserName() -> String {
return user.name!
}
// После рефакторинга
func getUserName() -> String {
return user.name ?? "Unknown"
}
2.2. Избавление от Massive View Controller (MVC → MVVM)
Legacy проекты часто страдают от "толстых" контроллеров. Выделите бизнес-логику в отдельные сервисы или ViewModel.
// Legacy ViewController (фрагмент)
class ProfileViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Бизнес-логика прямо в контроллере
let user = DataManager.shared.fetchUser()
nameLabel.text = user.name?.capitalized
}
}
// После рефакторинга
class ProfileViewModel {
private let userService: UserService
var userName: String {
return userService.currentUser?.name?.capitalized ?? "Guest"
}
init(userService: UserService) {
self.userService = userService
}
}
class ProfileViewController: UIViewController {
@IBOutlet weak var nameLabel: UILabel!
private let viewModel = ProfileViewModel(userService: UserService())
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = viewModel.userName
}
}
2.3. Миграция с Objective-C совместимости на чистый Swift
Если в проекте остались классы, наследующие NSObject только ради совместимости с ObjC, перепишите их на чистые Swift-структуры или классы без @objc.
// Legacy (ObjC-совместимость)
class LegacyModel: NSObject {
@objc var id: Int = 0
@objc var title: String = ""
}
// Рефакторинг
struct RefactoredModel {
let id: Int
let title: String
}
3. Инструменты Xcode для автоматизации рефакторинга
Xcode предоставляет мощные встроенные инструменты, которые ускоряют рефакторинг.
3.1. Refactor → Rename
Используйте Editor → Refactor → Rename (или Cmd+Ctrl+E) для переименования символов с учётом всех зависимостей. Это безопаснее ручного поиска и замены.
3.2. Extract Method
Выделите повторяющийся код в отдельные функции. Кликните правой кнопкой на блок кода → Refactor → Extract to Method.
3.3. Convert to Swift Concurrency
Если legacy код использует completion handlers или делегаты, Xcode 15+ может автоматически конвертировать их в async/await. Выберите метод → Refactor → Convert to Async.
// До: completion handler
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
// ...
}
// После: async/await
func fetchData() async throws -> Data {
// ...
}
4. Пошаговый план рефакторинга legacy модуля
Предположим, у вас есть старый класс UserManager с 500 строками, множеством force unwrap и прямой работой с UserDefaults. Вот как его можно реоргани