Os erros acontecem. Em Elixir, embora se diga muitas vezes para "let it crash", há alturas em que precisamos de resgatar a chamada à função para um estado bom conhecido, a fim de cumprir um contrato de software. Nalgumas linguagens, os erros são usados como método de controlo de fluxo, mas em Elixir este padrão é desaconselhado. Muitas vezes, conseguimos reconhecer funções que podem lançar um erro apenas pelo nome: as funções que lançam erros têm de ter ! no fim do nome. Isto, em comparação com funções que devolvem {:ok, value} ou :error. Repara nestes exemplos da biblioteca:
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
O Elixir disponibiliza uma construção para resgatar de erros usando try .. rescue
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
Vamos analisar esta construção:
try.raise/2.rescue, fazemos correspondência de padrões com o nome do módulo do erro lançado
->:
e corresponde à struct de erro.in é uma palavra-chave.RuntimeError é o erro que queremos resgatar.
_ em vez do nome do módulo ou omitir completamente a palavra-chave in.Os erros (às vezes também chamados "exceções") que resgatas desta forma são structs. Resgatar erros em Elixir é algo que se faz muito raramente. Normalmente, o erro resgatado é registado ou enviado para um serviço de monitorização externo, e depois é relançado. Isto significa que, normalmente, não nos preocupamos com a estrutura interna da struct de erro específica.
No conceito de Exceções vais aprender mais sobre structs de erro, incluindo como definir o teu próprio erro personalizado.
Enquanto trabalhas na Instruments of Texas, é-te pedido que trabalhes numa calculadora experimental de Notação Polaca Inversa [RPN] escrita em Elixir. A tua equipa tem um problema: algumas operações lançam erros e derrubam o processo. Foi-te pedido que escrevas uma função que encapsule a função de operação, para que os erros possam ser tratados de forma mais elegante com código Elixir idiomático.
Implementa 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 noutro local, mas sabes que pode ou terminar com sucesso ou lançar 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
Os nomes de funções que terminam em
!são um aviso aos programadores de que essa função pode lançar um erro
Ao fazeres mais pesquisa, reparas que muitas funções usam átomos e tuplos para indicar o sucesso ou a falha. Implementa calculate/2 com esta 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ção importante que os teus colegas precisam de ter para garantir o correto funcionamento do sistema. Implementa calculate_verbose/2 para transmitir 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"}
Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.