Fork me on GitHub

Tratamento de Erros

Some contents of this translation may be outdated.
Several major changes were applied to the original lesson since the last update.

Embora seja mais comum o retorno da tupla {: erro, reason}, Elixir suporta exceções e nesta lição veremos como lidar com erros e os diferentes mecanismos disponíveis para nós.

Em geral, a convenção em Elixir é criar uma função (example/1) que retorna {: ok, result} e {: error, reason} e uma função separada (example!/1) que retorna o result desembrulhado ou gerará um erro.

Esta lição irá focar interagindo com o último.

Sumário

Tratamento de Erros

Antes de podermos lidar com erros, precisamos criá-los e a maneira mais simples de fazer isso é com o raise/1:

iex> raise "Oh no!"
** (RuntimeError) Oh no!

Se queremos especificar o tipo e mensagem, precisamos usar raise/2:

iex> raise ArgumentError, message: "the argument value is invalid"
** (ArgumentError) the argument value is invalid

Se sabemos que um erro pode ocorrer, podemos lidar com isso usando try/rescue e padrões semelhantes:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> end
An error occurred: Oh no!
:ok

É possível combinar vários erros em um único rescue:

try do
  opts
  |> Keyword.fetch!(:source_file)
  |> File.read!
rescue
  e in KeyError -> IO.puts "missing :source_file option"
  e in File.Error -> IO.puts "unable to read source file"
end

Depois

Às vezes pode ser necessário para executar alguma ação depois de nossa try/rescue independentemente do erro, por isso temos try/after. Se você estiver familiarizado com Ruby este é semelhante ao begin/rescue/ensure ou em Java try/catch/finally:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> after
...>   IO.puts "The end!"
...> end
An error occurred: Oh no!
The end!
:ok

Esta é geralmente mais usada com arquivos ou conexões que devem ser fechados:

{:ok, file} = File.open "example.json"
try do
   # Do hazardous work
after
   File.close(file)
end

Novos Erros

Enquanto Elixir inclui uma série de tipos de erro nativos como RuntimeError, mantemos a capacidade de criar a nossa própria se precisamos de algo específico. Criar um novo erro é fácil com o defexception/1 macro que aceita convenientemente :message opções para definir uma mensagem de erro padrão:

defmodule ExampleError do
  defexception message: "an example error has occurred"
end

Vamos tentar forçar a execução do nosso novo erro:

iex> try do
...>   raise ExampleError
...> rescue
...>   e in ExampleError -> e
...> end
%ExampleError{message: "an example error has occurred"}

Lançar

Outro mecanismo para trabalhar com erros no Elixir é throw e catch. Na prática, isso não ocorre frequentemente no código mais recente de Elixir. Mas, é importante conhecer e entendê-los mesmo assim.

A função throw/1 nos dá a capacidade para sair de execução com um valor específico que podemos usar com o catch:

iex> try do
...>   for x <- 0..10 do
...>     if x == 5, do: throw(x)
...>     IO.puts(x)
...>   end
...> catch
...>   x -> "Caught: #{x}"
...> end
0
1
2
3
4
"Caught: 5"

Como mencionado, throw/catch são bastante incomuns e normalmente existem como tapa-buracos quando as bibliotecas não fornecem APIs adequadas.

Saindo

O mecanismo de erro final que Elixir fornece é o exit. Sinais de saída ocorrem sempre que um processo morre e são uma parte importante da tolerância a falhas do Elixir.

Para sair explicitamente podemos usar exit/1:

iex> spawn_link fn -> exit("oh no") end
** (EXIT from #PID<0.101.0>) "oh no"

Embora seja possível pegar uma saída com try/catch fazê-lo é extremamente raro. Em quase todos os casos, isso é vantajoso para permitir que o supervisor possa lidar com a saída do processo:

iex> try do
...>   exit "oh no!"
...> catch
...>   :exit, _ -> "exit blocked"
...> end
"exit blocked"

Compartilhe essa página