Er

Erros em Go

2 exercícios

Sobre Erros

A interface de erro

Em Go, o tratamento de erros não é feito através de exceções. Em vez disso, os erros são valores normais de tipos que implementam a interface error incorporada. A interface error é muito reduzida. Contém apenas um método, Error(), que devolve a mensagem de erro como uma string.

type error interface {
  Error() string
}

Sempre que defines uma função em que pode ocorrer um erro durante a execução que tenha de chegar a quem a chama, tens de incluir error como um dos tipos de retorno. Se a função tiver vários valores devolvidos, por convenção o error é sempre o último.

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

Criar e devolver um erro simples

Não tens de implementar sempre a interface de erro tu próprio. Para criar um erro simples, podes usar a função errors.New(), que faz parte do pacote errors da biblioteca padrão. A única coisa que tens de passar é a mensagem de erro como string, e o errors.New() encarrega-se de criar um valor que contém a tua mensagem e implementa a interface error.

Se a função devolver um erro, é boa prática devolver o valor zero para todos os outros valores de retorno:

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

Não deves assumir que todas as funções devolvem valores zero nos outros valores de retorno quando há um erro. É boa prática assumir que não é seguro usar nenhum dos outros valores de retorno se for devolvido um erro.

Por exemplo, imagina que uma função estava a tentar ler de um ficheiro e devolve uma slice de bytes meio preenchida e um erro. Se reutilizares essa slice de bytes devolvida no teu código, assumindo que está sempre vazia porque houve um erro, podes deparar-te com bugs subtis.

As únicas exceções são os casos em que a documentação da função indica claramente que os outros valores de retorno são relevantes no caso de um erro. Vê a [documentação de Write num buffer][buffer-write] para veres um exemplo.

Certifica-te de que a mensagem de erro que forneces é o mais específica possível, pois os erros não incluem stack traces por predefinição. Por convenção, a mensagem de erro deve começar com uma letra minúscula e não terminar com um ponto final.

Se quiseres usar esse erro em vários sítios (ou quiseres tornar o erro disponível para quem consome o teu pacote), deves declarar uma variável para o erro em vez de usares errors.New em linha. Por convenção, o nome da variável deve começar por Err ou err (consoante seja exportada ou não). Estas variáveis de erro são frequentemente chamadas erros sentinela.

import "errors"

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

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

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

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

Incluir variáveis na mensagem de erro

Se precisares de construir uma mensagem de erro mais complexa a partir de algumas variáveis, podes recorrer à formatação de strings. O Go tem uma função especial, fmt.Errorf, para esse efeito. Constrói a string da mensagem de erro e devolve 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 chamares uma função que devolve um erro, é comum guardar o valor do erro numa variável chamada err. Antes de usares o resultado real da função, tens de verificar que 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 "happy path" do teu código, os casos de erro devem ser tratados primeiro, normalmente através de um retorno antecipado. Vê 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 é devolvido ao longo da pilha de funções, como mostra o primeiro exemplo acima. Outra forma de tratar o erro pode ser registá-lo e continuar com outra operação. É boa prática devolver o erro ou registá-lo, nunca ambos.

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

Tipos de erro personalizados

Se quiseres que o teu erro inclua mais informação do que apenas a string da mensagem de erro, podes criar um tipo de erro personalizado. Como referido anteriormente, tudo o que implementa a interface error (ou seja, que tem um método Error() string) pode servir como erro em Go.

Normalmente, usa-se uma struct para criar um tipo de erro personalizado. Por convenção, os nomes dos tipos de erro personalizados devem terminar em Error. Além disso, é melhor definir o método Error() string com um recetor de ponteiro; vê este comentário no Stackoverflow para perceberes o raciocínio. Nota que isto significa que tens de devolver um ponteiro para o teu erro personalizado; caso contrário, não contará como error, porque o valor 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 A ligação abre numa nova janela ou separador

Aprende Erros

A prática está bloqueada

Desbloqueia mais 4 exercícios para praticares Erros