Los errores ocurren. En Elixir, aunque a menudo se dice que hay que «dejar que falle», hay ocasiones en las que necesitamos rescatar la llamada a la función hasta un estado conocido y válido para cumplir con un contrato de software. En algunos lenguajes, los errores se usan como método de flujo de control, pero en Elixir este patrón está desaconsejado. A menudo podemos reconocer las funciones que pueden lanzar un error solo por su nombre: las funciones que lanzan errores llevan un ! al final de su nombre. Esto contrasta con las funciones que devuelven {:ok, value} o :error. Mira estos ejemplos de la biblioteca:
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
Elixir proporciona una construcción para rescatar de errores usando try .. rescue.
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
Vamos a examinar esta construcción:
try.raise/2.rescue, hacemos coincidencia de patrones con el nombre del módulo del error lanzado
->:
e se asigna a la struct del error.in es una palabra clave.RuntimeError es el error que queremos rescatar.
_ en lugar del nombre del módulo u omitir la palabra clave in por completo.Los errores (a los que a veces también se llama «excepciones») que rescatas de esta forma son structs. En Elixir, rescatar errores se hace muy rara vez. Normalmente, el error rescatado se registra o se envía a un servicio de monitorización externo, y luego se vuelve a lanzar. Esto significa que normalmente no nos importa la estructura interna del struct de error concreto.
En el concepto de excepciones aprenderás más sobre los structs de error, incluido cómo definir tu propio error personalizado.
Mientras trabajas en Instruments of Texas, se te encarga trabajar en una calculadora experimental de notación polaca inversa [RPN] escrita en Elixir. Tu equipo tiene un problema: algunas operaciones lanzan errores y hacen que el proceso se caiga. Se te ha encargado escribir una función que envuelva la función de operación para que los errores se puedan gestionar de forma más elegante con código Elixir idiomático.
Implementa la función calculate!/2 para llamar a la función de operación con la pila como único argumento. La función de operación está definida en otro lugar, pero sabes que puede completarse correctamente o lanzar un error.
stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred
Los nombres de función que terminan en
!son un aviso para quien programa de que esa función puede lanzar un error
Cuando investigas un poco más, te das cuenta de que muchas funciones usan átomos y tuplas para indicar si tienen éxito o fallan. Implementa calculate/2 usando esta estrategia.
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error
Algunos de los errores contienen información importante que tus compañeros de trabajo necesitan tener para garantizar el correcto funcionamiento del sistema. Implementa calculate_verbose/2 para transmitir el mensaje de error. El error es un struct que tiene un campo :message.
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}
Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.