El tratamiento de errores en Elixir es un tema recurrente y fundamental para escribir código robusto. Aunque ya conocemos la forma habitual de manejar errores mediante funciones que devuelven tuplas con {:ok, resultado} o {:error, motivo}, no es la única manera. A veces, el código puede lanzar errores directamente, como cuando intentamos dividir un número entre cero, lo que genera un ArithmeticError. Estos errores vienen acompañados de mensajes descriptivos que nos ayudan a entender qué ha fallado y dónde.
En Elixir, la filosofía general es dejar que el sistema falle y se recupere reiniciando procesos, especialmente en entornos basados en OTP. Sin embargo, no siempre es necesario o conveniente reiniciar todo por un error trivial. En ocasiones, queremos capturar esos errores para manejarlos de forma más suave, ocultarlos o devolver valores alternativos que eviten que el fallo se propague.
Para esto, Elixir nos ofrece la construcción try-rescue. El bloque try intenta ejecutar un fragmento de código que puede ser problemático, y si ocurre un error, el bloque rescue nos permite capturarlo y decidir qué hacer con él. Esto nos da un control fino sobre los errores que queremos manejar y cómo queremos responder a ellos.
Veamos un ejemplo sencillo. Supongamos que queremos dividir 5 entre 0, lo cual sabemos que genera un error aritmético. Podemos envolver esta operación en un bloque try y usar rescue para capturar el error y devolver un valor alternativo, como :infinito:
result =
try do
x = 5
y = 0
x / y
rescue
ArithmeticError -> :infinito
end
IO.inspect(result)
En este código, si la división falla con un ArithmeticError, en lugar de que el programa se detenga, el bloque rescue intercepta el error y devuelve :infinito. Si no ocurre ningún error, el resultado normal de la división se asigna a result.
Además, podemos manejar varios tipos de errores dentro del mismo bloque rescue usando pattern matching, similar a cómo funciona un case. Por ejemplo, podríamos capturar un error de protocolo indefinido (UndefinedFunctionError) o incluso un caso genérico para atrapar cualquier otro error:
result =
try do
# Código que puede fallar
rescue
ArithmeticError -> :infinito
UndefinedFunctionError -> :funcion_no_definida
_error -> :error_desconocido
end
Otra característica útil es poder enlazar el error capturado a una variable para acceder a sus detalles, como el mensaje de error. Esto se hace usando la sintaxis variable in ErrorType. Por ejemplo:
result =
try do
5 / 0
rescue
e in ArithmeticError -> "Error aritmético: #{e.message}"
end
IO.puts(result)
Aquí, e es la variable que contiene la estructura del error, y podemos acceder a su mensaje con e.message. Esto nos permite construir respuestas más informativas o realizar acciones específicas según el contenido del error.
En definitiva, el uso de try-rescue en Elixir nos permite capturar errores que de otro modo detendrían la ejecución, manejar diferentes tipos de errores con pattern matching, y devolver valores alternativos o mensajes personalizados. Así, podemos escribir código más resiliente y controlado, evitando que fallos menores afecten la estabilidad general de nuestra aplicación.