Yeda AI Tips · #117

English

Rechaza el doble bang en Kotlin generado

Cada doble bang en Kotlin generado por IA es un crash esperando llegar a producción. El operador !! convierte cualquier valor a un tipo no anulable — y si el valor es null en ese momento, lanza una NullPointerException ahí mismo. La documentación de Kotlin lista !! como una de solo tres formas de provocar una NPE en código Kotlin puro. Cuando un asistente de IA lo esparce por el código generado, no está afirmando conocimiento: está silenciando al compilador porque es el camino más corto hacia código que compila.

Por qué el código generado ama !!

Los generadores de código optimizan para "compila al primer intento". Frente a un tipo anulable, las dos opciones honestas — propagar la anulabilidad o manejar la rama null — cuestan más código. !! cuesta dos caracteres. El mismo atajo aparece como el campo anulable de fase de inicialización: un var user: User? = null que "seguro se asigna antes de que alguien lo lea". Esa promesa vive en un comentario o en la cabeza del autor, no en el sistema de tipos, así que cada sitio de lectura o maneja un null que "no puede pasar" o le aplica doble bang. En ambos casos, la ausencia pasó de ser un hecho en tiempo de compilación a una apuesta en tiempo de ejecución.

Arreglo 1: haz la ausencia explícita y manéjala

Si un valor puede genuinamente faltar, mantén el tipo anulable y usa los operadores que el lenguaje te da:

// AI draft — deferred crash
val name = response.user!!.name

// Explicit — compiler-checked
val name = response.user?.name ?: "anonymous"

La llamada segura ?. devuelve null en lugar de lanzar una excepción; el operador Elvis ?: provee el valor por defecto. Las cadenas componen: bob?.department?.head?.name es null si cualquier eslabón es null — ninguna rama de esa expresión puede lanzar una NPE.

Arreglo 2: modela estados cerrados con resultados sealed

Cuando "null" representa un estado — aún no cargado, cargado, falló — un campo anulable es la herramienta equivocada. Una jerarquía sealed nombra cada estado, y como todas las subclases directas de una clase sealed se conocen en tiempo de compilación, el when sobre ella es exhaustivo sin else:

sealed interface LoadState {
    data object Loading : LoadState
    data class Ready(val user: User) : LoadState
    data class Failed(val cause: Exception) : LoadState
}

fun render(s: LoadState) = when (s) {
    LoadState.Loading   -> showSpinner()
    is LoadState.Ready  -> showUser(s.user)
    is LoadState.Failed -> showError(s.cause)
    // no else — the compiler proves every case is covered
}

Agrega un cuarto estado el próximo mes y cada when que lo olvidó se vuelve un error de compilación, no una alerta a las 3 a.m. Ese es el beneficio: el compilador te obliga a manejar el caso vacío, y clases enteras de crashes se vuelven inalcanzables.

Reglas prácticas para revisión

Ves en el diffPide en su lugar
foo!!.barfoo?.bar + fallback con ?:, o arreglar el tipo aguas arriba
var x: T? = null asignado "después" durante initInyección por constructor, o un estado sealed Loading/Ready
lateinit var leído antes de una asignación garantizadaGuard con ::x.isInitialized — o reestructurar el orden de init
when sobre un tipo sealed con rama elsewhen exhaustivo, sin else, para que casos nuevos rompan el build
Valor sin anotar de una API JavaTipo Kotlin explícito en la firma pública

Notas para usuarios avanzados

Recursos

¿Construyes con código generado por IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Hablemos · Lee el blog