Clean Code на Swift: принципы и примеры на практике

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

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

Начать курс

Clean Code на Swift: принципы и примеры на практике

Чистый код (Clean Code) — это не просто модный термин, а фундаментальный навык профессионального разработчика. В экосистеме Swift (iOS/macOS) качество кода напрямую влияет на скорость разработки, количество багов и удобство поддержки проекта. В этой статье мы разберем ключевые принципы Clean Code применительно к Swift и покажем их реализацию на практических примерах.

1. Именование: говорите на языке предметной области

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

Плохой пример:

func calc(a: [String: Any]) -> Double {
    var x = 0.0
    for (_, v) in a {
        if let n = v as? Double {
            x += n
        }
    }
    return x
}

Хороший пример (Clean Code):

func calculateTotalPrice(of products: [Product]) -> Double {
    return products.reduce(0) { $0 + $1.price }
}

Принципы именования в Swift:

  • Ясность важнее краткости: remove(at:) лучше, чем rem(at:).
  • Используйте существительные для типов: UserViewController, NetworkManager.
  • Глаголы для функций: fetchData(), saveToCache().
  • Булевы переменные: начинайте с is, has, should — isEnabled, hasContent.
  • Избегайте сокращений: backgroundColor вместо bgColor.

2. Принцип единственной ответственности (SRP) в Swift

Каждый класс, структура или функция должны иметь только одну причину для изменения. В iOS-разработке это особенно актуально для ViewController'ов, которые часто превращаются в "божественные объекты".

Нарушение SRP (типичный ViewController):

class ProfileViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var avatarImageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Загрузка данных
        let url = URL(string: "https://api.example.com/user")!
        let data = try! Data(contentsOf: url)
        let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
        nameLabel.text = json["name"] as? String
        
        // Загрузка аватара
        let imageUrl = URL(string: json["avatar"] as! String)!
        let imageData = try! Data(contentsOf: imageUrl)
        avatarImageView.image = UIImage(data: imageData)
        
        // Сохранение в кэш
        UserDefaults.standard.set(json, forKey: "cachedUser")
    }
}

Рефакторинг с соблюдением SRP:

// 1. Отдельный сервис для работы с сетью
class UserNetworkService {
    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
        // ... сетевая логика
    }
}

// 2. Отдельный менеджер кэша
class UserCacheManager {
    func saveUser(_ user: User) {
        // ... логика кэширования
    }
}

// 3. ViewModel для бизнес-логики
class ProfileViewModel {
    private let networkService = UserNetworkService()
    private let cacheManager = UserCacheManager()
    
    func loadProfile(completion: @escaping (User) -> Void) {
        networkService.fetchUser { [weak self] result in
            switch result {
            case .success(let user):
                self?.cacheManager.saveUser(user)
                completion(user)
            case .failure:
                // Загружаем из кэша при ошибке
                if let cachedUser = self?.cacheManager.loadUser() {
                    completion(cachedUser)
                }
            }
        }
    }
}

// 4. ViewController занимается только отображением
class ProfileViewController: UIViewController {
    private let viewModel = ProfileViewModel()
    @IBOutlet weak var nameLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.loadProfile { [weak self] user in
            DispatchQueue.main.async {
                self?.nameLabel.text = user.name
            }
        }
    }
}

3. Работа с опциональными значениями (Optionals)

Опциональные типы — мощный инструмент Swift, но при неправильном использовании они порождают "грязный" код. Принцип Clean Code требует избегать принудительного извлечения (!) и использовать безопасные паттерны.

Грязный код:

func getUserName() -> String {
    let userDict: [String: Any]? = getUserDictionary()
    let name: String? = userDict!["name"] as? String
    return name ?? "Unknown"
}

Чистый код с использованием guard и optional chaining:

func getUserName() -> String {
    guard let userDict = getUserDictionary(),
          let name = userDict["name"] as? String else {
        return "Unknown"
    }
    return name
}

Лучшие практики работы с Optionals:

  • Используйте guard для раннего выхода из функции при отсутствии значения.
  • Применяйте if let для безопасного извлечения в локальном контексте.
  • Избегайте force unwrap (!) — это почти всегда запах плохого кода.
  • Используйте ?? (nil-coalescing operator) для значений по умолчанию.

4. Функции: маленькие, предсказуемые, без побочных эффектов

В Clean Code функция должна делать только одно действие и быть достаточно маленькой, чтобы уместиться на экране (обычно 5-15 строк). В Swift это особенно важно при работе с замыканиями и Combine.

Плохой пример — функция делает всё:

func processUserData(userData: [String: Any]) {
    // Валидация
    guard let email = userData["email"] as? String, email.contains("@") else {
        print("Invalid email")
        return
    }
    
    // Преобразование
    let user = User(email: email, name: userData["name"] as? String ?? "")
    
    // Сохранение в Core Data
    let context = persistentContainer.viewContext
    let entity = UserEntity(context: context)
    entity.email = user.email
    entity.name = user.name
    try? context.save()
    
    // Обновление                

Блоги

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