Erros acontecem. No Elixir, embora muitas pessoas costumem dizer "let it crash", há momentos em que precisamos resgatar a chamada da função para um estado bom conhecido, a fim de cumprir um contrato de software. Em algumas linguagens, os erros são usados como forma de controle de fluxo, mas no Elixir esse padrão é desencorajado. Muitas vezes conseguimos reconhecer funções que podem lançar um erro só pelo nome: funções que lançam erros têm ! no final do nome. Isso em comparação com funções que retornam {:ok, value} ou :error. Veja estes exemplos da biblioteca:
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
O Elixir oferece uma construção para resgatar erros usando try .. rescue
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
Vamos examinar essa construção:
try.raise/2.rescue, fazemos pattern matching no nome do Módulo do erro lançado
->:
e corresponde à struct de erro.in é uma palavra-chave.RuntimeError é o erro que queremos resgatar.
_ no lugar do nome do módulo ou omitir completamente a palavra-chave in.Os erros (às vezes também chamados de "exceções") que você resgata dessa forma são structs. Resgatar erros no Elixir é algo muito raro. Normalmente, o erro resgatado é registrado em log ou enviado a um serviço externo de monitoramento e, em seguida, lançado novamente. Isso significa que geralmente não nos importamos com a estrutura interna da struct de erro específica.
No conceito de Exceptions, você vai aprender mais sobre structs de erro, incluindo como definir seu próprio erro personalizado.
Enquanto trabalha na Instruments of Texas, você é encarregado de trabalhar em uma calculadora experimental de Notação Polonesa Reversa [RPN] escrita em Elixir. Sua equipe está com um problema: algumas operações geram erros e travam o processo. Você foi encarregado de escrever uma função que encapsula a função de operação para que os erros possam ser tratados de forma mais elegante com código Elixir idiomático.
Implemente a função calculate!/2 para chamar a função de operação com a pilha como único argumento. A função de operação está definida em outro lugar, mas você sabe que ela pode ser concluída com sucesso ou gerar um erro.
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
Nomes de funções que terminam em
!são um aviso aos programadores de que essa função pode gerar um erro
Ao pesquisar mais, você percebe que muitas funções usam átomos e tuplas para indicar sucesso ou falha. Implemente calculate/2 usando essa estratégia.
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
Alguns dos erros contêm informações importantes que seus colegas de trabalho precisam ter para garantir o funcionamento correto do sistema. Implemente calculate_verbose/2 para repassar a mensagem de erro. O erro é uma struct que tem um 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"}
Crie sua conta no Exercism para aprender e dominar Elixir com 58 conceitos168 exercícios e mentoria humana de verdade, tudo de graça.