Manejo de errores en Kotlin: try/catch y técnicas avanzadas

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Manejo avanzado de errores en Kotlin: desde try/catch hasta enfoques funcionales



El manejo de errores es una de las tareas clave al desarrollar software confiable. En Kotlin, además del mecanismo clásico try/catch/finally, existe todo un arsenal de herramientas avanzadas: Result, runCatching, sealed class y mónadas funcionales. En este artículo, analizaremos cómo ir más allá del uso básico y hacer que tu código sea más seguro, legible y predecible.



1. Try/catch clásico: ¿cuándo sigue siendo apropiado?



El try/catch básico en Kotlin es una expresión que puede devolver un valor. Esto es lo primero que lo diferencia de Java.



val result: String = try {    riskyOperation()} catch (e: IOException) {    "Error de entrada/salida: ${e.message}"} catch (e: Exception) {    "Error desconocido: ${e.message}"} finally {    println("El bloque finally siempre se ejecuta")}


A pesar de su simplicidad, este enfoque tiene desventajas:

  • Las excepciones son una operación costosa (creación de stack trace).
  • El tipo de error no se fija a nivel de tipos: es fácil pasar por alto su manejo.
  • El código se vuelve voluminoso con una gran cantidad de operaciones anidadas.


¿Cuándo usar try/catch?

  • Al trabajar con recursos externos (archivos, red, bases de datos).
  • En código de bibliotecas, donde las excepciones son parte del contrato.
  • Para errores críticos que no se pueden manejar localmente.


2. Enfoque funcional: Result y runCatching



Kotlin ofrece la clase incorporada Result, que representa el resultado de una operación que puede terminar en éxito o error. Esta es una alternativa a las excepciones que no requiere un costoso stack trace.



fun divide(a: Int, b: Int): Result<Int> {    return runCatching {        a / b    }}

fun main() { val result = divide(10, 0) result.onSuccess { value -> println("Resultado: $value") }.onFailure { error -> println("Error: ${error.message}") }}


Métodos de Result

  • getOrNull() — devuelve el valor o null.
  • getOrDefault(default) — devuelve el valor o un valor por defecto.
  • map y flatMap — para cadenas de transformaciones.
  • recover — recuperación después de un error.


val safeResult = runCatching { riskyOperation() }    .map { it.toUpperCase() }    .recover { "default_value" }    .getOrDefault("fallback")


Importante: Result no está diseñado para transferirse entre hilos ni almacenarse en campos de clase — úsalo solo como un valor local.



3. Clases selladas: modelado de errores a nivel de tipos



Cuando necesitas definir claramente todos los estados posibles (éxito, diferentes tipos de errores), las sealed class vienen al rescate. Esta es una herramienta poderosa para crear tipos de datos algebraicos.



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 fetchUser(id: Int): NetworkResult<User> { return try { val user = api.getUser(id) NetworkResult.Success(user) } catch (e: HttpException) { NetworkResult.Error("El servidor devolvió un error", e.code()) } catch (e: Exception) { NetworkResult.Error("Error desconocido", 0) }}

fun handleResult(result: NetworkResult<User>) { when (result) { is NetworkResult.Success -> println("Usuario: ${result.data.name}") is NetworkResult.Er

Blogs

Book Recommendations