En Elixir, además de capturar errores con la palabra clave rescue, también podemos generar nuestros propios errores usando raise. Esto resulta especialmente útil cuando estamos desarrollando librerías o interfaces más sofisticadas y queremos evitar el uso de pattern matching o tuplas para manejar estados incorrectos.
Para ilustrarlo, imaginemos que tenemos una función dividir que toma dos argumentos, x y y. Queremos evitar que se produzca un error aritmético al dividir por cero. Si detectamos que y es igual a cero, podemos detener la ejecución y lanzar un error con raise. Por ejemplo, podemos usar un error predefinido como ArgumentError y acompañarlo de un mensaje personalizado que explique el problema:
def dividir(x, y) do
if y == 0 do
raise ArgumentError, message: "No me puedes dividir entre 0, por favor"
else
x / y
end
end
Si llamamos a dividir(5, 2), la función devolverá 2.5 sin problemas. Pero si intentamos dividir(5, 0), se lanzará un ArgumentError con el mensaje que hemos definido, deteniendo la ejecución y señalando el error.
Existe también una forma más sencilla de usar raise: pasarle directamente una cadena de texto. En ese caso, Elixir lanzará un RuntimeError, que es el error más genérico. Por ejemplo:
raise "Esto es un error genérico"
Aunque no es la forma más precisa, puede ser útil cuando queremos lanzar un error rápido sin definir un tipo específico.
Para crear errores personalizados, Elixir nos permite definir excepciones propias con def_exception. Esto se hace dentro de un módulo, donde podemos establecer un mensaje predeterminado para el error. Por ejemplo:
defmodule Calculadora.ErrorCalculo do
defexception message: "Se ha producido un error de cálculo"
end
Luego, podemos lanzar este error personalizado con:
raise Calculadora.ErrorCalculo
De esta manera, el error ya incluye un mensaje por defecto, y no es necesario especificarlo cada vez que lo usamos.
Además, en Elixir existen funciones que terminan con una exclamación, como File.read!. Estas funciones lanzan un error automáticamente si algo falla, en lugar de devolver una tupla con el resultado y el error. Por ejemplo, File.read("archivo.txt") devuelve {:ok, contenido} o {:error, razón}, mientras que File.read!("archivo.txt") devuelve directamente el contenido o lanza un error si el archivo no existe.
Este comportamiento es útil cuando estamos seguros de que la operación debería funcionar y preferimos que el proceso se detenga en caso de error, sin necesidad de manejarlo explícitamente. Sin embargo, debemos tener en cuenta que si ocurre un error, la excepción se propagará y tendremos que capturarla o aceptar que el proceso falle.
Este patrón se repite en varias funciones de Elixir, como fetch!, pop! y otras, donde la versión con exclamación lanza errores en lugar de devolver valores que indiquen fallo.
Por último, aunque Elixir también tiene la construcción throw para lanzar valores y abortar la ejecución de una función, su uso es menos común y más especializado. Se puede capturar con catch, pero generalmente es recomendable evitarlo y preferir raise y rescue para el manejo de errores.
Así, en Elixir contamos con varias herramientas para generar y manejar errores de forma clara y estructurada, desde lanzar errores genéricos o personalizados con raise, hasta aprovechar funciones que lanzan excepciones automáticamente para simplificar el código cuando estamos seguros del éxito de una operación.