Elixir - Manejo de errores

Elixir tiene tres mecanismos de error: errores, lanzamientos y salidas. Exploremos cada mecanismo en detalle.

Error

Los errores (o excepciones) se utilizan cuando suceden cosas excepcionales en el código. Se puede recuperar un error de muestra al intentar agregar un número en una cadena:

IO.puts(1 + "Hello")

Cuando se ejecuta el programa anterior, produce el siguiente error:

** (ArithmeticError) bad argument in arithmetic expression
   :erlang.+(1, "Hello")

Este fue un error incorporado de muestra.

Generando errores

Podemos raiseerrores al usar las funciones de aumento. Consideremos un ejemplo para entender lo mismo:

#Runtime Error with just a message
raise "oops"  # ** (RuntimeError) oops

Se pueden generar otros errores con raise / 2 pasando el nombre del error y una lista de argumentos de palabras clave

#Other error type with a message
raise ArgumentError, message: "invalid argument foo"

También puede definir sus propios errores y plantearlos. Considere el siguiente ejemplo:

defmodule MyError do
   defexception message: "default message"
end

raise MyError  # Raises error with default message
raise MyError, message: "custom message"  # Raises error with custom message

Rescatar errores

No queremos que nuestros programas se cierren abruptamente, sino que los errores deben manejarse con cuidado. Para ello utilizamos el manejo de errores. Nosotrosrescue errores usando el try/rescueconstruir. Consideremos el siguiente ejemplo para entender lo mismo:

err = try do
   raise "oops"
rescue
   e in RuntimeError -> e
end

IO.puts(err.message)

Cuando se ejecuta el programa anterior, produce el siguiente resultado:

oops

Hemos manejado errores en la declaración de rescate usando la coincidencia de patrones. Si no tenemos ningún uso del error, y solo queremos usarlo con fines de identificación, también podemos usar el formulario -

err = try do
   1 + "Hello"
rescue
   RuntimeError -> "You've got a runtime error!"
   ArithmeticError -> "You've got a Argument error!"
end

IO.puts(err)

Cuando se ejecuta el programa anterior, produce el siguiente resultado:

You've got a Argument error!

NOTE- La mayoría de las funciones de la biblioteca estándar de Elixir se implementan dos veces, una devuelve tuplas y la otra genera errores. Por ejemplo, elFile.read y el File.read!funciones. El primero devolvió una tupla si el archivo se leyó correctamente y si se encontró un error, esta tupla se usó para dar la razón del error. El segundo generó un error si se encontró un error.

Si usamos el enfoque de la primera función, entonces necesitamos usar el caso para el patrón que coincide con el error y tomar medidas de acuerdo con eso. En el segundo caso, utilizamos el enfoque de prueba de rescate para el código propenso a errores y manejamos los errores en consecuencia.

Lanza

En Elixir, se puede lanzar un valor y luego ser capturado. Lanzar y atrapar están reservados para situaciones en las que no es posible recuperar un valor a menos que se utilice lanzar y atrapar.

Las instancias son bastante poco comunes en la práctica, excepto cuando interactúan con bibliotecas. Por ejemplo, supongamos ahora que el módulo Enum no proporcionó ninguna API para encontrar un valor y que necesitábamos encontrar el primer múltiplo de 13 en una lista de números:

val = try do
   Enum.each 20..100, fn(x) ->
      if rem(x, 13) == 0, do: throw(x)
   end
   "Got nothing"
catch
   x -> "Got #{x}"
end

IO.puts(val)

Cuando se ejecuta el programa anterior, produce el siguiente resultado:

Got 26

Salida

Cuando un proceso muere por "causas naturales" (por ejemplo, excepciones no controladas), envía una señal de salida. Un proceso también puede morir al enviar explícitamente una señal de salida. Consideremos el siguiente ejemplo:

spawn_link fn -> exit(1) end

En el ejemplo anterior, el proceso vinculado murió al enviar una señal de salida con un valor de 1. Tenga en cuenta que la salida también se puede "capturar" usando try / catch. Por ejemplo

val = try do
   exit "I am exiting"
catch
   :exit, _ -> "not really"
end

IO.puts(val)

Cuando se ejecuta el programa anterior, produce el siguiente resultado:

not really

Después

A veces es necesario asegurarse de que un recurso se limpia después de alguna acción que potencialmente puede generar un error. La construcción try / after le permite hacer eso. Por ejemplo, podemos abrir un archivo y usar una cláusula after para cerrarlo, incluso si algo sale mal.

{:ok, file} = File.open "sample", [:utf8, :write]
try do
   IO.write file, "olá"
   raise "oops, something went wrong"
after
   File.close(file)
end

Cuando ejecutamos este programa, nos dará un error. Pero elafter La declaración asegurará que el descriptor de archivo se cierre ante tal evento.