Introducción: Por qué el "código limpio" no va de estética
Cuando empiezas a escribir en Kotlin, parece que el propio lenguaje ya resuelve la mitad de los problemas. Sin código repetitivo como en Java. Tienes `data class`, lambdas, corrutinas. Pero si simplemente acumulas código "sobre la marcha", en un mes no podrás entenderlo tú mismo. Y si un equipo trabaja en el proyecto, tus colegas te odiarán (o tú a ellos).
El código limpio en Kotlin no va de tabulaciones frente a espacios. Se trata de reducir la carga cognitiva, hacer la lógica transparente y evitar errores que se esconden en construcciones "cómodas". En este artículo te mostraré técnicas concretas: desde trabajar con nullable hasta usar scope functions y sealed classes. Sin rodeos, solo práctica.
1. Null Safety: cómo no multiplicar "?" y "!!"
Kotlin se enorgullece de su sistema de tipos, pero muchos escriben como si fuera Java con "azúcar sintáctico". El principal antipatrón son las cadenas de `?.let` y los `!!` de pánico.
Malo (la legibilidad sufre):
user?.address?.city?.let { city -> println(city)} ?: run { println("No city")}Bueno (elegante y limpio):
val city = user?.address?.cityprintln(city ?: "No city")Usa `!!` solo si estás 100% seguro de que el valor no es null (por ejemplo, en pruebas o después de una comprobación con `if`). En producción, es una granada. Si necesitas convertir un nullable en no nulo y manejar ambos casos, usa when o el operador Elvis.
2. Scope Functions: `apply`, `run`, `with`, `let`, `also` — ¿cuándo usar cada una?
Son una herramienta potente, pero a menudo se abusa de ellas. La regla de oro: no anides scope functions unas dentro de otras. Si tienes `list.apply { ... }.let { ... }`, estás escribiendo código espagueti.
Reglas de selección:
apply— para configurar un objeto (devuelve el propio objeto). Ideal para constructores.let— para ejecutar una acción con un valor no nulo (a menudo en combinación con `?.`).run— cuando necesitas calcular un valor y devolver un resultado.with— para agrupar llamadas a un mismo objeto sin devolver nada.also— para efectos secundarios (registro, validación).
Ejemplo limpio:
data class User(var name: String, var age: Int)
fun createUser(): User = User("Unknown", 0).apply { name = "Alice" age = 30}No escribas user?.let { it.name } — es redundante. Simplemente usa user?.name.
3. Extension Functions: no multipliques clases de utilidades
En Java escribirías StringUtils.capitalize(str). En Kotlin — str.capitalize(). Las funciones de extensión son una forma de "completar" una clase ajena sin heredar de ella.
Ejemplo de extensión útil:
fun String.isValidEmail(): Boolean { return this.contains("@") && this.contains(".")}Pero no te excedas. Si una función de extensión se usa solo en un lugar, quizás debería ser privada. Y no hagas extensiones sobre `Any` — eso rompe el sistema de tipos.
4. Sealed Classes y When: tu herramienta principal en lugar de if-else
Cuando tienes un conjunto finito de estados (por ejemplo, el resultado de una solicitud API), usa sealed class. Esto garantiza que has manejado todas las variantes.
Enfoque correcto:
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error(val message: String, val code: Int) : NetworkResult<Nothing>() object Loading : NetworkResult<Nothing>()}
fun handle(result: NetworkResult<String>) { when (result) { is NetworkResult.Success -> println("Data: ${result.data}") is NetworkResult.Error -> println("Error: ${resu