Curso Kotlin | #15. Excepciones

Las excepciones en Kotlin nos permiten evitar comportamientos no deseados ante problemas que se presentan en tiempo de ejecución. Por ejemplo cuando se intenta dividir por cero, castear un tipo por otro no válido, intentar agregar un valor a una lista nula, entre otros casos. Hoy vamos a ver su implementación en el código.

Manos a la obra

Usemos un ejemplo sencillo de calificaciones: si te doy más de 5 puntos estas aprobado; caso contrario estas reprobado:

print("Cual es tu nota final? > ")
val eval = readLine()!!

if(eval.toInt() >= 6){
    println("Estas aprobado")
} else {
    println("Has reprobado")
}

Si no entiendes alguna parte, deberías pasarte por la clase de condicionales. En caso contrario, verás que lidiamos con un ejemplo muy sencillo, el cual arroja un resultado según la condicion del número. En la variable eval utilizé el bang bang operator (!!) asumiendo que no mi variable será nula y simplificando la parte de la expresión en nuestra condición.

Cual es tu nota final? > 5
Has reprobado

Pero, ¿qué ocurre si en vez de un número, escribo una palabra?:

Cual es tu nota final? > mostaza
Exception in thread "main" java.lang.NumberFormatException: For input string: "mostaza"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
	at MainKt.main(main.kt:8)

Dado que el mundo todavía no esta preparado para calificar a la gente con aderezos, recibimos un NumberFormatException, la cual indica que no puede convertir mostaza a un número entero.

Bloque Try Catch

No queremos que nuestro programa explote cuando ocurra una excepción: necesitamos capturarla y continuar el flujo normalmente. Para ello existe el bloque Try Catch, y lo implementaremos de la siguiente manera:

try {
    if(eval.toInt() >= 6){
        println("Estas aprobado")
    } else {
        println("Has reprobado")
    }
} catch (e: Exception) {
    println("$eval no es un número valido :(")
}

Dentro del cuerpo de try debemos ingresar las lineas que serán evaluadas y podrían tener una potencial excepción. Aquí es donde muchos desarrolladores cometen errores, dado que escriben medio programa dentro del bloque, lo cual es innecesario. Solo debemos ingresar lo que consideremos que podría generar una excepción. Luego en catch debemos especificar al programa que ocurrirá cuando se presente la excepción. El parámetro e de tipo Exception (clase padre de todas las excepciones) contiene el stack que vimos arriba, entre otra información útil que podremos devolver si fuera necesario. Nosotros solo indicaremos un mensaje de error:

Cual es tu nota final? > mostaza
mostaza no es un número valido :(

Esto funcionará bien siempre con “mostaza”, pero imaginemos que luego de obtener la calificación, la dividiremos por cero, porque estamos dementes:

println("Hora de dividir por cero! :D")
val res = eval.toInt() / 0

Este bloque de código irá al final de nuestro try.

Cual es tu nota final? > 6
Estas aprobado
Hora de dividir por cero! :D
6 no es un número valido :(

Como vemos, el comportamiento de nuestro programa es extraño: primero nos explica que estamos aprobados, pero luego dice que “6” no es un número válido. Esto ocurre porque no estamos distinguiendo entre excepciones. Por suerte, podemos especificarlo de la siguiente manera:

print("Cual es tu nota final? > ")
val eval = readLine()!!

try {
    if(eval.toInt() >= 6){
        println("Estas aprobado")
    } else {
        println("Has reprobado")
    }

    println("Hora de dividir por cero! :D")
    val res = eval.toInt() / 0
} catch (e: NumberFormatException) {
    println("$eval no es un número valido :(")
} catch (e: ArithmeticException) {
    println("$eval no se puede dividir por cero!")
}

Podemos utilizar todos los catch que necesitemos, siempre y cuando nos enfoquemos en las excepciones que queramos evaluar:

Cual es tu nota final? > 6
Estas aprobado
Hora de dividir por cero! :D
6 no se puede dividir por cero!

Recordemos que las excepciones se disparan en el momento en que se ejecuta la linea correspondiente dentro del try y luego salta al catch. Esto funcionaría como un break, por lo cual todo lo que haya debajo no se ejecuta. El disparador en este caso sería eval.toInt() / 0, pero si escribimos un texto:

Cual es tu nota final? > patata
patata no es un número valido :(

La excepción se va a disparar en if(eval.toInt() >= 6){. Esto ocurre antes de dividir por cero, porque nuestro número no es válido.

A romper cosas

Capturar excepciones es importante para que nuestro programa no nos explote en la cara, pero también podemos romper cosas nosotros. Esto será muy útil cuando desarrollemos librerías y queramos arrojar nuestras propias excepciones en la cara de otros desarrolladores.

Imaginemos que creamos un programa que requiere un nombre, pero si llega vacío, lanzaremos un error:

print("Cual es tu nombre? >")
val name = readLine()

if(name.isNullOrEmpty()){
    throw Exception("El nombre no puede estar vacío!")
} else {
    print("Hola $name!")
}

El método isNullOrEmpty pregunta si la variable es nula o esta vacía. Esto solo podemos utilizarlo con los tipo nullable. Si corremos el programa y no introducimos un nombre, nos devolverá lo siguiente:

Cual es tu nombre? >
Exception in thread "main" java.lang.Exception: El nombre no puede estar vacío!
	at MainKt.main(main.kt:12)

Con la sentencia throw y el tipo de excepción, hemos logrado devolver un mensaje claro y detallado del error. Algo curioso es que también nos indica la linea donde se disparó la excepción (12 en mi caso).

Conclusiones

Voy a donarles un pequeño secreto a voces en el mundo del desarrollo: La verdadera diferencia entre un desarrollador junior y uno más experimentado, es el manejo inteligente de excepciones. Si se centran en evitar que sus programas exploten y controlar el momento en el que sea importante lanzar una, van a tener un buen dominio del flujo de ejecución de sus aplicaciones. Este tema se extenderá en las siguientes clases, especialmente cuando implementemos funciones y el paradigma de la programación orientada a objetos.

¡Deja un comentario!

Artículos relacionados

Curso Kotlin | #19. Pair

A veces necesitamos relacionar dos valores y almacenarlos en una variable única, y para ello tenemos a Pair.

Curso Kotlin | #17. Proyecto: The Hero Legacy

Si empezaste esta serie desde cero y ya leíste 16 capítulos, ¡Felicidades! Empezaste a dominar los primeros conceptos fundamentales en Kotlin. Este es el primer paso de tu senda como desarrollador/a. Estamos en una instancia en donde podemos poner en práctica todo lo que aprendiste hasta ahora. Bienvenido/a al primer proyecto de la serie.

Curso Kotlin | #16. Funciones

Las funciones son procedimientos que se pueden reutilizar y nos permiten encapsular comportamientos y mejorar la lectura del código.

Curso Kotlin | #14. Rangos

Cuando hablamos de rangos, nos referimos a un intervalo de números en el sentido matemático. Podemos generar, por ejemplo, un rango que comprenda los números del 1 al 10. Hoy veremos como aplicarlo a los conceptos anteriormente aprendidos.

Curso Kotlin | #13. Bucle For

El bucle For es uno de los más utilizados por todos los lenguajes de programación. Nos permite recorrer arrays y listas, como también generar iteraciones con un límite contabilizado. Vamos a verlo en detalle.