O tratamento de erros em Go é baseado em valores explícitos, não em exceções. A linguagem adota uma abordagem pragmática onde erros são retornados como valores comuns, exigindo que o programador os verifique ativamente.
Estrutura Básica do Erro
O tipo error é uma interface simples:
type error interface {
Error() string
}
Para criar erros, usamos errors.New ou fmt.Errorf:
func New(msg string) error {
return &erroSimples{mensagem: msg}
}
type erroSimples struct {
mensagem string
}
func (e *erroSimples) Error() string {
return e.mensagem
}
Comparação de Erros
Erros em Go são comparados por referência, não por valor textual. Dois erros criados com o mesmo texto não são iguais se forem instâncias distintas:
err1 := errors.New("falha")
err2 := &erroSimples{"falha"}
fmt.Println(err1 == err2) // false
Mas se forem valores (não ponteiros), a comparação pode ser verdadeira:
type erroValor struct { s string }
func (e erroValor) Error() string { return e.s }
func novoErro(s string) error {
return erroValor{s}
}
errA := novoErro("teste")
errB := novoErro("teste")
fmt.Println(errA == errB) // true
Evolução Histórica
- C: Usava códigos de retorno inteiros.
- C++: Introduziu exceções, mas sem contrato explícito.
- Java: Checked exceptions obrigavam tratamento, mas saturaram o código.
- Go: Optou por múltiplos retornos e
panicapenas para falhas fatais.
Exemplo Prático de Refatoração
Antes — lógica incorreta ao tratar zero:
func EhPositivo(x int) bool {
return x >= 0
}
Depois — tratamento explícito com erro:
func EhPositivo(x int) (bool, error) {
if x == 0 {
return false, errors.New("valor neutro")
}
return x > 0, nil
}
Sentinel Errors
Erros predefinidos como io.EOF são chamados de sentinelas. Embora úteis, criam acoplamento entre pacotes e dificultam a adição de contexto. Evite-os quando possível.
Tipos de Erro Personalizados
Você pode definir tipos que implementam error para carregar metadados:
type ErroComContexto struct {
Arquivo string
Linha int
Detalhe string
}
func (e *ErroComContexto) Error() string {
return fmt.Sprintf("%s:%d: %s", e.Arquivo, e.Linha, e.Detalhe)
}
Isso permite extração de dados via type assertion, mas também aumenta o acoplamento da API.
Erros Opaquos
A forma mais flexível: trate o erro como um valor opaco. Não inspecione seu conteúdo interno — apenas propague-o ou registre-o. Use interfaces comportamentais quando necessário:
type temporario interface {
Temporario() bool
}
func ehTemporario(err error) bool {
t, ok := err.(temporario)
return ok && t.Temporario()
}
Encadeamento de Erros com Contexto
Pacotes como github.com/pkg/errors permitem empilhar cotnexto sem perder a causa raiz:
func lerArquivo(caminho string) ([]byte, error) {
f, err := os.Open(caminho)
if err != nil {
return nil, errors.Wrap(err, "falha ao abrir arquivo")
}
defer f.Close()
conteudo, err := io.ReadAll(f)
if err != nil {
return nil, errors.Wrap(err, "falha ao ler conteúdo")
}
return conteudo, nil
}
Na camada superior, imprima o rastreamento completo:
if err != nil {
fmt.Printf("ERRO: %+v\n", err)
}
Novidades do Go 1.13+
O Go 1.13 introduziu suporte nativo para erros encadeados usando %w:
if err != nil {
return fmt.Errorf("operação %s: %w", nome, err)
}
E funções para inspeção:
if errors.Is(err, os.ErrNotExist) { ... }
var qErr *QueryError
if errors.As(err, &qErr) { ... }
Princípios Essenciais
- Trate cada erro apenas uma vez.
- Não logue e retorne o mesmo erro — isso gera duplicação.
- Propague erros com contexto até o ponto onde podem ser tratados.
- Bibliotecas reutilizáveis devem retornar erros raiz; aplicações podem encapsular.
- Após tratar um erro, não o propague — retorne
nilou valor alteernativo.