Tratamento de erros
Nesta aula, exploramos o tratamento de erros em Go, que é explícito e baseado no tipo error. Aprendemos a usar a verificação if err != nil, criar erros com errors.New e fmt.Errorf, e seguir as práticas idiomáticas da linguagem.
Em Go, o tratamento de erros é uma parte fundamental da linguagem, projetado para ser explícito e simples, sem exceções ou mecanismos complexos. Diferente de outras linguagens que usam try-catch, Go trata erros como valores comuns que podem ser retornados e verificados. Isso encoraja o programador a lidar com erros de forma direta, tornando o código mais previsível e fácil de depurar.
Nesta aula, vamos mergulhar no tipo error, na prática idiomática de verificar erros com if err != nil, na criação de erros personalizados e nas convenções que tornam o código Go claro e consistente.
O tipo error
Em Go, error é uma interface nativa definida como:
type error interface {
Error() string
}Qualquer tipo que implemente o método Error() string satisfaz a interface error. Isso significa que você pode criar seus próprios tipos de erro, desde que eles tenham esse método. A simplicidade da interface permite que erros sejam valores leves e flexíveis.
Funções que podem falhar geralmente retornam um valor do tipo error como último valor de retorno. Por convenção, se a operação for bem-sucedida, o erro é nil; caso contrário, contém informações sobre o problema.
if err != nil
A verificação if err != nil é a maneira mais comum de tratar erros em Go. Após chamar uma função que retorna um erro, você verifica se o erro é diferente de nil e age de acordo. Esse padrão é tão frequente que se tornou uma marca registrada do código Go.
Exemplo típico:
f, err := os.Open("arquivo.txt")
if err != nil {
log.Fatal(err) // ou trate o erro de outra forma
}
defer f.Close()
// usa o arquivo...É importante tratar o erro imediatamente, sem ignorá-lo. Ignorar erros com _ pode levar a comportamentos inesperados e é considerado uma má prática, a menos que você tenha certeza absoluta de que o erro é irrelevante.
Criando erros (errors.New, fmt.Errorf)
Para criar seus próprios erros, a biblioteca padrão oferece duas funções principais:
errors.New(texto string) error: Cria um erro simples com a mensagem fornecida.fmt.Errorf(format string, a ...interface{}) error: Permite formatar a mensagem de erro comfmt.Sprintf, retornando umerror.
Exemplo com errors.New:
err := errors.New("ocorreu um erro")
fmt.Println(err) // ocorreu um erroExemplo com fmt.Errorf (mais flexível):
nome := "João"
idade := 30
err := fmt.Errorf("%s tem %d anos", nome, idade)
fmt.Println(err) // João tem 30 anosAmbas as funções retornam um valor que implementa a interface error. Use errors.New para mensagens estáticas e fmt.Errorf quando precisar de formatação.
Idiomático
Go possui convenções claras para tratamento de erros que tornam o código consistente e legível:
- Erro como último valor de retorno: Funções que podem falhar retornam o erro como último parâmetro.
- Verificação imediata: Após a chamada, verifique
if err != nilantes de prosseguir. - Mensagens de erro em letra minúscula: Por convenção, as mensagens de erro não começam com letra maiúscula nem terminam com pontuação, a menos que incluam nomes próprios.
- Erros sentinela: Use variáveis de erro globais para erros específicos (ex.:
var ErrNotFound = errors.New("not found")). - Empacotamento de erros: Use
fmt.Errorfcom%wpara criar uma cadeia de erros (wrapping), permitindo queerrors.Iseerrors.Asfuncionem.
Exemplo de empacotamento:
if err != nil {
return fmt.Errorf("falha ao processar: %w", err)
}Isso preserva o erro original para inspeção posterior.
Boas práticas
Algumas dicas para escrever código Go idiomático no tratamento de erros:
- Não use
panicpara erros comuns; reserve-o para situações excepcionais que indicam bugs. - Documente os erros que sua função pode retornar, especialmente se forem erros sentinela.
- Evite repetir o contexto do erro; use wrapping para adicionar informações relevantes.
- Prefira tratar erros no nível mais adequado (não propague erros desnecessariamente para camadas superiores).
Referências
- Error handling and Go - The Go Blog
- Package errors - Go Documentation
- Package fmt - func Errorf
- Effective Go - Errors
- Go Wiki: Errors
Exercícios
Crie uma função chamada
divide(a, b int) (int, error)que retorna um erro sebfor zero, usandoerrors.New. Em seguida, chame a função e trate o erro.✓ Resposta:package main import ( "errors" "fmt" ) func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("divisão por zero") } return a / b, nil } func main() { result, err := divide(10, 0) if err != nil { fmt.Println("Erro:", err) } else { fmt.Println("Resultado:", result) } }Modifique a função
dividepara usarfmt.Errorfe incluir os valores deaebna mensagem de erro.✓ Resposta:func divide(a, b int) (int, error) { if b == 0 { return 0, fmt.Errorf("divisão por zero: %d / %d", a, b) } return a / b, nil }Crie um erro sentinela chamado
ErrDivisionByZeroe use-o na funçãodivide. Depois, verifique se o erro é igual a esse sentinela usandoerrors.Is.✓ Resposta:package main import ( "errors" "fmt" ) var ErrDivisionByZero = errors.New("divisão por zero") func divide(a, b int) (int, error) { if b == 0 { return 0, ErrDivisionByZero } return a / b, nil } func main() { _, err := divide(10, 0) if errors.Is(err, ErrDivisionByZero) { fmt.Println("Erro: divisão por zero detectada") } }Implemente uma função
readFile(filename string) (string, error)que tenta ler um arquivo e retorna seu conteúdo como string. Useos.ReadFilee empacote o erro com contexto usandofmt.Errorfcom%w.✓ Resposta:package main import ( "fmt" "os" ) func readFile(filename string) (string, error) { data, err := os.ReadFile(filename) if err != nil { return "", fmt.Errorf("falha ao ler arquivo %s: %w", filename, err) } return string(data), nil }Crie um tipo de erro personalizado chamado
ValidationErrorque tenha camposFieldeMessage. Implemente o métodoError() string. Depois, use esse tipo em uma função de validação.✓ Resposta:package main import "fmt" type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validação falhou no campo %s: %s", e.Field, e.Message) } func validateName(name string) error { if name == "" { return &ValidationError{Field: "name", Message: "não pode ser vazio"} } return nil } func main() { err := validateName("") if err != nil { fmt.Println(err) } }