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()
// Обновление