¿Por qué la depuración de Kotlin es un arte y no una rutina?
Imagine: escribe un código elegante en Kotlin, usa corrutinas, null-safety y funciones de extensión. Todo parece perfecto hasta que la aplicación falla con un NullPointerException en el lugar más inesperado. O peor aún, una corrutina simplemente se "duerme" para siempre, sin dejar rastro. ¿Le suena familiar?
La depuración de código Kotlin no es solo buscar errores. Es la habilidad de "espiar" el funcionamiento de su aplicación en tiempo real, entender cómo funcionan las corrutinas y encontrar rápidamente los cuellos de botella. En este artículo, analizaremos técnicas avanzadas de depuración de Kotlin que le ahorrarán horas de estrés y harán que su código sea más estable.
Herramientas básicas de depuración de Kotlin en IntelliJ IDEA
IntelliJ IDEA no es solo un IDE, es una navaja suiza para el desarrollador Kotlin. Empecemos con lo que está en la superficie, pero que a menudo se usa incorrectamente.
Puntos de interrupción: más que una simple pausa
Un punto de interrupción normal (line breakpoint) es un clásico. Pero Kotlin ofrece mucho más:
- Conditional breakpoints — se activan solo cuando se cumple una condición. Por ejemplo, detenerse cuando
userId == 42. - Exception breakpoints — capturan excepciones antes de que caigan en el registro.
- Method breakpoints — se activan al entrar y salir de una función. Ideal para depurar cadenas de llamadas complejas.
Ejemplo de configuración de un conditional breakpoint:
fun processOrder(order: Order) { // Colocamos un breakpoint y añadimos la condición: order.amount > 1000 val discount = calculateDiscount(order.amount) // ...}Simplemente haga clic derecho en el punto de interrupción e ingrese la condición. ¡Ahorra mucho tiempo!
Depuración de corrutinas: mirando dentro del mundo async
Las corrutinas son potentes, pero la depuración puede convertirse en una pesadilla. IntelliJ IDEA (a partir de la versión 2020.3) ofrece el Coroutines Debugger. Muestra:
- El estado de cada corrutina (Active, Suspended, Completed).
- La pila de llamadas para cada corrutina.
- Los valores de las variables locales dentro de la corrutina.
import kotlinx.coroutines.*
fun main() = runBlocking { val job1 = launch { delay(1000) println("Job 1 done") } val job2 = launch { delay(500) println("Job 2 done") } // Coloque un breakpoint aquí y mire la pestaña "Coroutines" job1.join() job2.join()}Consejo: active la opción Show coroutines en el panel de depuración; le ahorrará dolores de cabeza con el código asíncrono.
Técnicas avanzadas: depuración sin dolor
Ahora pasemos a las técnicas que diferencian a un novato de un profesional.
El registro como primera línea de defensa
No siempre se puede colocar un punto de interrupción (por ejemplo, en producción). Aquí es donde el registro viene al rescate. Pero no un simple println(), sino un registro significativo.
import org.slf4j.LoggerFactory
private val log = LoggerFactory.getLogger(MyService::class.java)
fun fetchUserData(userId: String): User? { log.debug("Fetching user data for userId: {}", userId) return try { val user = repository.findById(userId) log.info("User found: {}", user?.name) user } catch (e: Exception) { log.error("Failed to fetch user: {}", userId, e) null }}Use niveles de registro: TRACE para detalles, DEBUG para depuración, INFO para eventos importantes. Y nunca deje println() en producción; es de mala educación.
Perfilado de memoria: caza de fugas
Kotlin, gracias a la JVM, es propenso a fugas de memoria. Use el Profiler integrado en IntelliJ IDEA