Tratamento de Erros em Go: filosofia e prática
Ao contrário de muitas linguagens modernas, Go (Golang) não usa a construção clássica try/catch. Em vez disso, os desenvolvedores Go adotam a abordagem de "erros como valores" (errors as values). Isso torna o código mais explícito, previsível e seguro. Neste artigo, abordaremos todos os aspectos do tratamento de erros em Go: desde padrões básicos até técnicas avançadas com defer, panic e recover.
1. Fundamentos: erros são apenas valores
Em Go, um erro é uma interface error com um único método Error() string. Qualquer função que pode falhar retorna um erro como o último valor de retorno.
package main
import ( "errors" "fmt")
// Função de divisão com retorno de errofunc divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("divisão por zero impossível") } return a / b, nil}
func main() { result, err := divide(10, 0) if err != nil { fmt.Println("Erro:", err) return } fmt.Println("Resultado:", result)}Ponto chave: a verificação if err != nil é a forma padrão de tratar erros em Go. Não há exceções ocultas que possam "surgir" inesperadamente.
2. Criação e customização de erros
Go fornece várias formas de criar erros:
errors.New()— erro textual simples.fmt.Errorf()— erro com formatação (suporta%wpara wrapping).- Tipos personalizados — para transmitir contexto adicional.
Erro personalizado com dados adicionais
type DivisionError struct { Dividend float64 Divisor float64 Message string}
func (e *DivisionError) Error() string { return fmt.Sprintf("%s: %f / %f", e.Message, e.Dividend, e.Divisor)}
func safeDivide(a, b float64) (float64, error) { if b == 0 { return 0, &DivisionError{ Dividend: a, Divisor: b, Message: "divisão por zero", } } return a / b, nil}
func main() { _, err := safeDivide(10, 0) if err != nil { // Type assertion para obter campos adicionais var divErr *DivisionError if errors.As(err, &divErr) { fmt.Printf("Erro com %f por %f: %s", divErr.Dividend, divErr.Divisor, divErr.Message) } }}O uso de errors.As() permite verificar se o erro é de um tipo específico e extrair dados dele. É o equivalente ao instanceof em outras linguagens.
3. Padrões de tratamento de erros: do simples ao complexo
Em Go, existem vários padrões estabelecidos para trabalhar com erros:
3.1. Sentinel errors (erros marcadores)
São erros predefinidos que podem ser comparados com ==. Exemplo: io.EOF.
import ( "errors" "fmt")
var ErrNotFound = errors.New("elemento não encontrado")
func findUser(id int) (string, error) { if id != 1 { return "", ErrNotFound } return "Alice", nil}
func main() { _, err := findUser(2) if errors.Is(err, ErrNotFound) { fmt.Println("Usuário não encontrado, criando novo...") }}3.2. Wrapping de erros (encapsulamento)
A partir do Go 1.13, é possível encapsular erros usando %w em fmt.Errorf. Isso preserva a cadeia de erros.
func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("leitura do config %s: %w", path, err) } // ... processamento data