Elixir - Tratamento de Erros

Elixir tem três mecanismos de erro: erros, lançamentos e saídas. Vamos explorar cada mecanismo em detalhes.

Erro

Erros (ou exceções) são usados ​​quando coisas excepcionais acontecem no código. Um exemplo de erro pode ser recuperado ao tentar adicionar um número em uma string -

IO.puts(1 + "Hello")

Quando o programa acima é executado, ele produz o seguinte erro -

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

Este foi um exemplo de erro embutido.

Levantando Erros

Podemos raiseerros usando as funções de aumento. Vamos considerar um exemplo para entender o mesmo -

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

Outros erros podem ser gerados com raise / 2 passando o nome do erro e uma lista de argumentos de palavras-chave

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

Você também pode definir seus próprios erros e aumentá-los. Considere o seguinte exemplo -

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

Resgatando Erros

Não queremos que nossos programas sejam encerrados abruptamente, mas sim que os erros devem ser tratados com cuidado. Para isso, usamos tratamento de erros. Nósrescue erros usando o try/rescueconstruir. Vamos considerar o seguinte exemplo para entender o mesmo -

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

IO.puts(err.message)

Quando o programa acima é executado, ele produz o seguinte resultado -

oops

Tratamos os erros na instrução de resgate usando correspondência de padrões. Se não tivermos nenhum uso do erro, e apenas quisermos usá-lo para fins de identificação, também podemos usar o formulário -

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

IO.puts(err)

Ao executar o programa acima, ele produz o seguinte resultado -

You've got a Argument error!

NOTE- A maioria das funções na biblioteca padrão Elixir são implementadas duas vezes, uma retornando tuplas e a outra gerando erros. Por exemplo, oFile.read e a File.read!funções. O primeiro retornou uma tupla se o arquivo foi lido com sucesso e se um erro foi encontrado, esta tupla foi usada para dar o motivo do erro. O segundo gerava um erro se um erro fosse encontrado.

Se usarmos a abordagem da primeira função, então precisamos usar o caso para o padrão que corresponde ao erro e agir de acordo com isso. No segundo caso, usamos a abordagem de recuperação de tentativa para código sujeito a erros e tratamos os erros de acordo.

Lança

No Elixir, um valor pode ser lançado e depois capturado. Throw e Catch são reservados para situações em que não é possível recuperar um valor, a menos que use throw e catch.

As instâncias são bastante incomuns na prática, exceto na interface com bibliotecas. Por exemplo, vamos supor que o módulo Enum não forneceu nenhuma API para encontrar um valor e que precisávamos encontrar o primeiro múltiplo de 13 em uma 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)

Quando o programa acima é executado, ele produz o seguinte resultado -

Got 26

Saída

Quando um processo morre de “causas naturais” (por exemplo, exceções não tratadas), ele envia um sinal de saída. Um processo também pode morrer enviando explicitamente um sinal de saída. Vamos considerar o seguinte exemplo -

spawn_link fn -> exit(1) end

No exemplo acima, o processo vinculado morreu enviando um sinal de saída com valor de 1. Observe que a saída também pode ser “capturada” usando try / catch. Por exemplo -

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

IO.puts(val)

Quando o programa acima é executado, ele produz o seguinte resultado -

not really

Depois de

Às vezes, é necessário garantir que um recurso seja limpo após alguma ação que pode potencialmente gerar um erro. A construção try / after permite que você faça isso. Por exemplo, podemos abrir um arquivo e usar uma cláusula after para fechá-lo - mesmo se algo der errado.

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

Quando executarmos este programa, ele nos dará um erro. Mas oafter declaração irá garantir que o descritor de arquivo seja fechado em qualquer evento.