Tratamento de Erros em Go: Práticas e Evolução

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 panic apenas 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 nil ou valor alteernativo.

Tags: go error-handling golang-errors

Publicado em 10-3 02:26