Рефакторинг Swift кода: когда он нужен и как делать правильно

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ваш код работает, но это не повод его не трогать

Вы написали приложение, оно запускается, клиенты довольны, билды не падают. Казалось бы, можно выдохнуть и заняться новыми фичами. Но внутри кодовой базы — монстр Франкенштейна: методы на 300 строк, копипаста, запутанная логика и магические числа, разбросанные по всему проекту. Если вы хотя бы раз ловили себя на мысли «этот класс лучше не трогать, а то всё сломается» — рефакторинг Swift кода для вас не просто рекомендация, а жизненная необходимость.

В этой статье мы разберем, когда действительно стоит браться за рефакторинг, а когда лучше оставить код в покое. Я покажу конкретные примеры на Swift, которые вы сможете применить уже сегодня. И да, никакой воды — только практика.

Что такое рефакторинг и чем он не является

Начнем с базы. Рефакторинг Swift кода — это процесс изменения внутренней структуры программы без изменения её внешнего поведения. Вы не добавляете новые функции, не чините баги (хотя часто находите их по пути). Вы просто делаете код чище, понятнее и легче для поддержки.

Важно понимать: рефакторинг ≠ переписывание с нуля. Если вам хочется выбросить весь проект и начать заново на SwiftUI с архитектурой TCA — это не рефакторинг, это переписывание. А переписывание, как показывает опыт, в 80% случаев заканчивается провалом.

Когда делать рефакторинг Swift кода: три верных сигнала

1. Правило трёх (Rule of Three)

Если вы копируете один и тот же фрагмент кода в третий раз — это сигнал. Не во второй, а именно в третий. Первый раз — ок, вы решили задачу. Второй раз — уже сомнительно, но можно списать на спешку. Третий раз — всё, пиши обобщенное решение.

// Плохо: копипаста
func formatUserName(_ name: String) -> String {
    return name.trimmingCharacters(in: .whitespaces).capitalized
}

func formatProductName(_ name: String) -> String {
    return name.trimmingCharacters(in: .whitespaces).capitalized
}

// Хорошо: единый метод
func formatDisplayName(_ name: String) -> String {
    return name.trimmingCharacters(in: .whitespaces).capitalized
}

2. «Запах» длинных методов

Если метод не помещается на экран телефона — это проблема. В Swift сообществе принято ориентироваться на 15-20 строк на метод. Конечно, бывают исключения (сложные вычисления), но в 95% случаев длинный метод — признак того, что он делает слишком много.

3. Тестирование превращается в ад

Когда для тестирования одной функции нужно мокать 5 сервисов, настраивать базу данных и читать файл с диска — это красный флаг. Рефакторинг Swift кода должен сделать вашу кодовую базу более тестируемой. Если сейчас тесты писать больно — значит, архитектура просит перемен.

Практические примеры рефакторинга Swift

Давайте посмотрим на реальные ситуации, с которыми сталкивается каждый iOS-разработчик.

Пример 1: Избавляемся от магических чисел

Магические числа — это числовые литералы, значение которых непонятно без контекста. Они делают код хрупким и нечитаемым.

// До рефакторинга
func calculateDiscount(price: Double) -> Double {
    if price > 1000 {
        return price * 0.15
    } else if price > 500 {
        return price * 0.1
    } else {
        return price * 0.05
    }
}

// После рефакторинга
private struct DiscountConstants {
    static let premiumThreshold: Double = 1000
    static let standardThreshold: Double = 500
    static let premiumRate: Double = 0.15
    static let standardRate: Double = 0.10
    static let basicRate: Double = 0.05
}

func calculateDiscount(price: Double) -> Double {
    switch price {
    case _ where price > DiscountConstants.premiumThreshold:
        return price * DiscountConstants.premiumRate
    case _ where price > DiscountConstants.standardThreshold:
        return price * DiscountConstants.standardRate
    default:
        return price * DiscountConstants.basicRate
    }
}

Пример 2: Разделение ответственности (SRP)

Один из самых частых случаев рефакторинга Swift кода — классы, которые делают всё. Сетевой запрос, парсинг, сохранение в БД и обновление UI — всё в одном методе.

// До: монстр-метод
class UserManager {
    func loadAndDisplayUser(id: String) {
        // 1. Сетевой запрос
        let url = URL(string: "https://api.example.com/user/\(id)")!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            // 2. Парсинг
            guard let data = data,
                  let user = try? JSONDecoder().decode(User.self, from: data) else {
                return
            }
            
            // 3. Сохранение в кэш
            UserDefaults.standard.set(user.name, forKey: "lastUserName")
            
            // 4. Обновление UI
            DispatchQueue.main.async {
                self.updateUI(with: user)
            }
        }.resume()
    }
}

// После: разделяем ответственность
class UserNetworkService {
    func fetchUser(id: String) async throws -> User {
        let url = URL(string: "https://api.example.com/user/\(id)")!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(User.self, from: data)
    }
}

class UserCacheService {
    func cacheUser(_ user: User) {
        UserDefaults.standard.set(user.name, forKey: "lastUserName")
    }
}

class UserViewModel {
    private let networkService: UserNetworkService
    private let cacheService: UserCacheService
    
    init(networkService: UserNetworkService, cacheService: UserCacheService) {
        self.networkService = networkService
        self.cacheService = cacheService
    }
    
    func loadUser(id: String) async throws -> User {
        let user = try await networkService.fetchUser(id: id)
        cacheService.cacheUser(user)
        return user
    }
}

Пример 3: Замена условных операторов полиморфизмом

Это классический приём рефакторинга Swift кода, который убирает длинные switch-case и if-else цепочки.

// До: куча условий
enum NotificationType {
    case email, sms, push
}

func sendNotification(_ type: NotificationType, message: String) {
    switch type {
    case .email:
        print("Sending email: \(message)")
        // 30 строк кода для email
    case .sms:
        print("Sending SMS: \(message)")
        // 30 строк кода для SMS
    case .push:
        print("Sending push: \(message)")
        // 30 строк кода для push
    }
}

// После: протокол и полиморфизм
protocol NotificationSender {
    func send(message: String)
}

class EmailSender: NotificationSender {
    func send(message: String) {
        print("Sending email: \(message)")
        // вся логика здесь
    }
}

class SMSSender: NotificationSender {
    func send(message: String) {
        print("Sending SMS: \(message)")
        // вся логика здесь
    }
}

class PushSender                

Блоги

Рекомендаций по книгам