Error handling: explicit + errors.Is/As
- ⬜🧩 Interfaces pequenas + composition(Go Profissional)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Erros são valores, não exceções
| Situação | Mecanismo | Por que este e não outro |
|---|---|---|
| Falha esperada sem dado extra | Erro sentinela comparado por identidade | Quem trata só precisa saber QUAL erro foi |
| Falha esperada com dado | Tipo de erro inspecionado por asserção | O tratamento precisa do campo: qual coluna, qual código do banco |
| Acrescentar contexto | Embrulhar preservando a cadeia | Mantém a comparação funcionando três camadas acima |
| Várias falhas ao mesmo tempo | Juntar num erro composto | Encerramento de aplicação fecha vários recursos; um erro não pode esconder os outros |
| Invariante violado | Pânico | Continuar em estado corrompido é pior que parar — mas só para bug, nunca para fluxo |
Go trata erro como tipo de retorno. Cada chamada que pode falhar retorna (resultado, error). A ausência de exceptions é decisão de design: o fluxo é linear, visível, e o compilador não deixa você esquecer de tratar. O "custo" é o famoso if err != nil — o benefício é controle total sobre comportamento de falha.
Padrão base
func loadConfig(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf("read config: %w", err)
}
var c Config
if err := json.Unmarshal(data, &c); err != nil {
return Config{}, fmt.Errorf("parse config: %w", err)
}
return c, nil
}%w faz wrap (cadeia), %v só formata. Use %w sempre que quiser manter a cadeia inspecionável por errors.Is/As.
Sentinel errors e tipos customizados
// Sentinel: valor de erro conhecido, comparado por identidade
var ErrNotFound = errors.New("not found")
// Tipo: quando precisa de dados extras
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return "invalid " + e.Field + ": " + e.Message
}errors.Is e errors.As na prática
row, err := db.QueryRowContext(ctx, q, id).Scan(&u.Name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return User{}, ErrNotFound
}
var pqErr *pq.Error
if errors.As(err, &pqErr) && pqErr.Code == "23505" {
return User{}, ErrDuplicate
}
return User{}, fmt.Errorf("scan user: %w", err)
}Is para comparar valor, As para inspecionar tipo e campos. Ambos caminham pela cadeia criada por %w — você não precisa desembrulhar manualmente.
Qual é a diferença prática entre embrulhar o erro com `%w` e com `%v`?
errors.Join (Go 1.20)
func (a *App) Shutdown(ctx context.Context) error {
return errors.Join(
a.http.Shutdown(ctx),
a.db.Close(),
a.cache.Close(),
a.tracer.Shutdown(ctx),
)
}Antes você escrevia multiError custom em cada projeto. Hoje a stdlib resolve, e errors.Is continua atravessando cada um deles.
panic/recover com cuidado
// recover em middleware de HTTP protege servidor de panic em handler
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if p := recover(); p != nil {
slog.Error("panic", "val", p, "stack", string(debug.Stack()))
http.Error(w, "internal", 500)
}
}()
next.ServeHTTP(w, r)
})
}recover é aceitável em fronteira (servidor HTTP, worker pool). Dentro da lógica de domínio, trate erro como valor. Panic como fluxo normal é antipadrão e confunde leitor.
Regra prática
Sentinel para erro sem dado. Tipo para erro com dado. %w sempre que embrulhar. errors.Is/As na comparação. panic só para bug. Esse conjunto escala de CLI até microsserviço sem surpresa.
Perguntas frequentes
❓ Como envolver erro sem perder a origem?
❓ Quando comparar por valor e quando por tipo?
❓ O tratamento explícito não deixa o código repetitivo?
Fixando
Quando usar um erro sentinela e quando usar um tipo de erro?
Onde `recover` é aceitável, e por quê?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…