Clean Code en Swift: principios y ejemplos prácticos
\n\nEl código limpio (Clean Code) no es solo un término de moda, sino una habilidad fundamental para cualquier desarrollador profesional. En el ecosistema Swift (iOS/macOS), la calidad del código afecta directamente la velocidad de desarrollo, la cantidad de errores y la facilidad de mantenimiento del proyecto. En este artículo, analizaremos los principios clave de Clean Code aplicados a Swift y mostraremos su implementación con ejemplos prácticos.
\n\n1. Nomenclatura: hable el lenguaje del dominio
\n\nUn buen nombre de variable, función o clase reemplaza los comentarios. En Swift, con su rico sistema de tipos y valores opcionales, la nomenclatura correcta es especialmente importante.
\n\nEjemplo incorrecto:
\nfunc calc(a: [String: Any]) -> Double {\n var x = 0.0\n for (_, v) in a {\n if let n = v as? Double {\n x += n\n }\n }\n return x\n}\n\nEjemplo correcto (Clean Code):
\nfunc calculateTotalPrice(of products: [Product]) -> Double {\n return products.reduce(0) { $0 + $1.price }\n}\n\nPrincipios de nomenclatura en Swift:
\n- \n
- La claridad es más importante que la brevedad:
remove(at:)es mejor querem(at:). \n - Use sustantivos para tipos:
UserViewController,NetworkManager. \n - Verbos para funciones:
fetchData(),saveToCache(). \n - Variables booleanas: comience con
is,has,should—isEnabled,hasContent. \n - Evite abreviaturas:
backgroundColoren lugar debgColor. \n
2. Principio de responsabilidad única (SRP) en Swift
\n\nCada clase, estructura o función debe tener una sola razón para cambiar. En el desarrollo de iOS, esto es especialmente relevante para los ViewControllers, que a menudo se convierten en "objetos divinos".
\n\nViolación de SRP (ViewController típico):
\nclass ProfileViewController: UIViewController {\n @IBOutlet weak var nameLabel: UILabel!\n @IBOutlet weak var avatarImageView: UIImageView!\n \n override func viewDidLoad() {\n super.viewDidLoad()\n // Carga de datos\n let url = URL(string: "https://api.example.com/user")!\n let data = try! Data(contentsOf: url)\n let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]\n nameLabel.text = json["name"] as? String\n \n // Carga de avatar\n let imageUrl = URL(string: json["avatar"] as! String)!\n let imageData = try! Data(contentsOf: imageUrl)\n avatarImageView.image = UIImage(data: imageData)\n \n // Guardar en caché\n UserDefaults.standard.set(json, forKey: "cachedUser")\n }\n}\n\nRefactorización cumpliendo SRP:
\n// 1. Servicio separado para trabajar con la red\nclass UserNetworkService {\n func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {\n // ... lógica de red\n }\n}\n\n// 2. Gestor de caché separado\nclass UserCacheManager {\n func saveUser(_ user: User) {\n // ... lógica de caché\n }\n}\n\n// 3. ViewModel para la lógica de negocio\nclass ProfileViewModel {\n private let networkService = UserNetworkService()\n private let cacheManager = UserCacheManager()\n \n func loadProfile(completion: @escaping (User) -> Void) {\n networkService.fetchUser { [weak self] result in\n switch result {\n case .success(let user):\n self?.cacheManager.saveUser(user)\n completion(user)\n case .failure:\n // Cargar desde caché en caso de error\n if let cachedUser = self?.cacheManager.lo