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.