Er

Erros em Go

2 exercícios

Sobre Erros

A interface de erro

Em Go, o tratamento de erros não é feito por meio de exceções. Em vez disso, os erros são valores normais de tipos que implementam a interface error embutida. A interface error é bem minimalista. Ela contém apenas um método Error(), que retorna a mensagem de erro como uma string.

type error interface {
  Error() string
}

Sempre que você define uma função em que um erro pode acontecer durante a execução e precisa chegar até quem chamou, você precisa incluir error como um dos tipos de retorno. Se a função tem vários valores de retorno, por convenção error é sempre o último.

func DoSomething() (int, error) {
  // ...
}

Criando e retornando um erro simples

Você não precisa implementar a interface de erro por conta própria todas as vezes. Para criar um erro simples, você pode usar a função errors.New(), que faz parte do pacote errors da biblioteca padrão. A única coisa que você precisa passar é a mensagem de erro como uma string, e errors.New() se encarrega de criar um valor que contém a sua mensagem e implementa a interface error.

Se a função retorna um erro, é uma boa prática retornar o valor zero para todos os outros parâmetros de retorno:

func DoSomething() (SomeStruct, int, error) {
  // ...
  return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Caution

Você não deve presumir que todas as funções retornam valores zero para os outros valores de retorno quando há um erro. A melhor prática é presumir que não é seguro usar nenhum dos outros valores de retorno se um erro for retornado.

Como exemplo, imagine uma função tentando ler de um arquivo e que retorna um slice de bytes parcialmente preenchido e um erro. Se você reutilizar esse slice de bytes retornado no seu código, presumindo que ele está sempre vazio porque houve um erro, você pode acabar com bugs sutis.

As únicas exceções são os casos em que a documentação da função afirma claramente que outros valores de retorno são significativos em caso de erro. Veja a [documentação de Write em um buffer][buffer-write] para um exemplo.

Certifique-se de que a mensagem de erro que você fornece seja o mais específica possível, já que os erros não incluem stack traces por padrão. Por convenção, a mensagem de erro deve começar com uma letra minúscula e não terminar com ponto final.

Se você quer usar esse erro em vários lugares (ou quer deixar o erro disponível para quem consome o seu pacote), você deve declarar uma variável para o erro em vez de usar errors.New inline. Por convenção, o nome da variável deve começar com Err ou err (dependendo de ela ser exportada ou não). Essas variáveis de erro costumam ser chamadas de erros sentinela.

import "errors"

var ErrNotFound = errors.New("resource was not found")

func DoSomething() error {
  // ...
  return ErrNotFound
}

Retorne nil no erro para sinalizar que não houve erros durante a execução da função:

func Foo() (int, error) {
  return 10, nil
}

Incluindo variáveis na mensagem de erro

Se você precisa montar uma mensagem de erro mais complexa a partir de algumas variáveis, pode usar formatação de strings. Go tem uma função especial fmt.Errorf para isso. Ela constrói a string da mensagem de erro e retorna um erro.

input := 123
action := "UPDATE"

err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"

Verificação de erros

Se você chama uma função que retorna um erro, é comum guardar o valor do erro em uma variável chamada err. Antes de usar o resultado de fato da função, você precisa verificar se não houve erro. Podemos usar == e != para comparar o erro com nil, e sabemos que houve um erro quando err não é nil.

Para evitar aninhar o "caminho feliz" do seu código, os casos de erro devem ser tratados primeiro, geralmente com um retorno antecipado. Veja este famoso artigo do Mat Ryer para mais detalhes.

// Do this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err != nil {
    return err
  }
  // do something with file
}

// Not this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err != nil {
    // handle err
  } else {
    // do something with file
  }
}

// Also not this:
func myFunc() error {
  file, err := os.Open("./users.csv")
  if err == nil {
    // do something with file
  }
  // handle err
}

Na maioria das vezes, o erro será retornado para cima na pilha de funções, como mostrado no primeiro exemplo acima. Outra forma de tratar o erro poderia ser registrá-lo no log e seguir com alguma outra operação. É uma boa prática ou retornar ou registrar o erro, nunca as duas coisas.

Como a maioria das funções em Go inclui um erro como um dos valores de retorno, você vai ver e usar o padrão if err != nil por toda parte no código Go.

Tipos de erro personalizados

Se você quer que o seu erro inclua mais informações do que apenas a string da mensagem de erro, você pode criar um tipo de erro personalizado. Como mencionado antes, tudo que implementa a interface error (ou seja, tem um método Error() string) pode servir como um erro em Go.

Normalmente, usa-se uma struct para criar um tipo de erro personalizado. Por convenção, os nomes de tipos de erro personalizados devem terminar com Error. Além disso, é melhor definir o método Error() string com um receptor de ponteiro; veja este comentário no Stackoverflow para entender o motivo. Repare que isso significa que você precisa retornar um ponteiro para o seu erro personalizado, caso contrário ele não conta como error, porque o valor que não é ponteiro não fornece o método Error() string.

type MyCustomError struct {
  message string
  details string
}

func (e *MyCustomError) Error() string {
  return fmt.Sprintf("%s, details: %s", e.message, e.details)
}

func someFunction() error {
  // ...
  return &MyCustomError{
    message: "...",
    details: "...",
  }
}
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Erros

A prática está bloqueada

Desbloqueie mais 4 exercícios para praticar Erros