Erros em Go: o tipo error e a convenção de retorno
1. Filosofia de erros em Go: sem exceções, tudo é valor
1.1. Diferença fundamental entre Go e linguagens com try/catch
Diferente de linguagens como Java, Python ou JavaScript, Go não possui mecanismos de exceção (try/catch/finally). Em vez disso, Go adota uma abordagem explícita e direta: erros são valores comuns que retornam das funções. Essa filosofia elimina a necessidade de blocos especiais de tratamento e torna o fluxo de erro visível no código.
1.2. Erro como retorno explícito: controle de fluxo no código
Em Go, o erro é tratado como qualquer outro valor de retorno. Isso força o programador a lidar com o erro no ponto onde ele ocorre, tornando o código mais previsível e legível. Não há surpresas: se uma função pode falhar, ela retorna um erro.
1.3. A importância da verificação constante: if err != nil
O padrão mais comum em Go é verificar erros imediatamente após chamar uma função que pode falhar:
file, err := os.Open("arquivo.txt")
if err != nil {
// trata o erro
return err
}
defer file.Close()
Essa verificação constante pode parecer repetitiva, mas é intencional: cada erro deve ser tratado ou propagado explicitamente.
2. O tipo error como interface nativa
2.1. Definição da interface error: o método Error() string
O tipo error é uma interface nativa do Go, definida como:
type error interface {
Error() string
}
Qualquer tipo que implemente o método Error() que retorna uma string satisfaz automaticamente essa interface.
2.2. Qualquer tipo que implemente Error() é um erro
Isso significa que você pode criar seus próprios tipos de erro personalizados:
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("erro %d: %s", e.Code, e.Message)
}
2.3. O valor nil como representação de "sem erro"
O valor nil representa a ausência de erro. Funções que executam com sucesso retornam nil no lugar do erro. Lembre-se: error é uma interface, então nil só é verdadeiro quando tanto o tipo quanto o valor são nil.
var err error = nil
fmt.Println(err == nil) // true
var e *MyError = nil
err = e
fmt.Println(err == nil) // false! (interface com tipo não-nil)
3. Convenção de retorno de erros em funções
3.1. Padrão: último valor de retorno é error
A convenção estabelece que o erro deve ser o último valor retornado por uma função:
func Dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("divisão por zero")
}
return a / b, nil
}
3.2. Retornar nil para sucesso, valor não-nil para falha
func BuscarUsuario(id int) (*Usuario, error) {
if id <= 0 {
return nil, errors.New("ID inválido")
}
usuario := &Usuario{ID: id, Nome: "João"}
return usuario, nil
}
3.3. Exemplos práticos: funções que retornam (T, error)
func ConverteInteiro(s string) (int, error) {
valor, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("falha ao converter '%s': %w", s, err)
}
return valor, nil
}
4. Criando erros simples com errors.New e fmt.Errorf
4.1. errors.New("mensagem"): erros literais
import "errors"
var ErrSaldoInsuficiente = errors.New("saldo insuficiente para saque")
4.2. fmt.Errorf("formato %v", valor): erros com formatação
func Sacar(conta *Conta, valor float64) error {
if valor > conta.Saldo {
return fmt.Errorf("saque de %.2f excede saldo de %.2f", valor, conta.Saldo)
}
conta.Saldo -= valor
return nil
}
4.3. Boas práticas: mensagens descritivas e sem capitalização
Mensagens de erro em Go geralmente começam com letra minúscula e não terminam com pontuação, para facilitar a composição com outras mensagens.
5. Inspeção e comparação de erros
5.1. Comparação direta com ==: limitações
Comparar erros com == funciona apenas para erros idênticos (mesmo ponteiro):
if err == ErrSaldoInsuficiente {
// trata erro específico
}
5.2. Uso de errors.Is para verificar sentinel errors
errors.Is verifica se um erro corresponde a um erro alvo, percorrendo a cadeia de wrappers:
if errors.Is(err, ErrSaldoInsuficiente) {
fmt.Println("Erro de saldo insuficiente detectado")
}
5.3. Uso de errors.As para verificar tipos específicos
errors.As encontra o primeiro erro na cadeia que corresponde a um tipo específico:
var netErr *net.DNSError
if errors.As(err, &netErr) {
fmt.Printf("Erro de DNS: %v\n", netErr)
}
6. Erros customizados: implementando a interface error
6.1. Structs que implementam Error() string
type ErroHTTP struct {
StatusCode int
Mensagem string
}
func (e *ErroHTTP) Error() string {
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Mensagem)
}
6.2. Adicionando campos extras (código HTTP, detalhes, etc.)
func FazerRequisicao(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("requisição falhou: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &ErroHTTP{
StatusCode: resp.StatusCode,
Mensagem: "resposta não esperada",
}
}
return io.ReadAll(resp.Body)
}
6.3. Quando criar um tipo customizado vs. usar erros simples
Use erros simples (errors.New / fmt.Errorf) para mensagens descartáveis. Crie tipos customizados quando precisar de informações adicionais para tratamento específico.
7. Sentinela de erros e boas práticas de design
7.1. Definindo sentinel errors como variáveis de pacote
package banco
var (
ErrSaldoInsuficiente = errors.New("saldo insuficiente")
ErrContaBloqueada = errors.New("conta bloqueada")
ErrValorInvalido = errors.New("valor inválido para operação")
)
7.2. Evitando exposição de detalhes internos
Não exponha detalhes de implementação nos erros. Use wrapping seletivo:
func processarDados() error {
err := acessoInterno()
if err != nil {
return fmt.Errorf("processamento falhou: %w", err)
}
return nil
}
7.3. Erros como parte da API pública: documentação e previsibilidade
Documente quais erros sua função pode retornar. Isso ajuda quem consome sua API a tratar casos de erro adequadamente.
8. Padrões avançados de retorno e tratamento
8.1. Retorno múltiplo com erro: defer e cleanup em caso de falha
func ProcessarArquivo(nome string) error {
f, err := os.Open(nome)
if err != nil {
return err
}
defer f.Close()
// Se algo falhar, o defer garante o fechamento
dados, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("leitura falhou: %w", err)
}
return processar(dados)
}
8.2. Erros em funções variádicas e closures
func SomarValores(valores ...int) (int, error) {
if len(valores) == 0 {
return 0, errors.New("pelo menos um valor é necessário")
}
total := 0
for _, v := range valores {
if v < 0 {
return 0, fmt.Errorf("valor negativo não permitido: %d", v)
}
total += v
}
return total, nil
}
8.3. Tratamento silencioso vs. propagação: quando ignorar ou repassar
// Ignorar intencionalmente (raro e documentado)
_, _ = fmt.Println("ignorando erro") // não faça isso sem motivo
// Propagar com contexto adicional
if err := operacao(); err != nil {
return fmt.Errorf("etapa crítica falhou: %w", err)
}
Referências
- Documentação oficial do pacote errors — Referência completa da biblioteca padrão para criação e manipulação de erros em Go.
- Effective Go: Errors — Guia oficial do Go sobre boas práticas no tratamento de erros.
- Go Blog: Error handling and Go — Artigo oficial do time Go sobre filosofia e técnicas de tratamento de erros.
- Go Blog: Working with Errors in Go 1.13 — Introdução às funcionalidades de wrapping e unwrapping de erros introduzidas no Go 1.13.
- Dave Cheney: Go errors — Artigo clássico sobre tratamento elegante de erros em Go por Dave Cheney.
- The Go Programming Language Specification: Errors — Especificação técnica do tipo
errorna linguagem Go. - Go by Example: Errors — Exemplos práticos e comentados de uso de erros em Go.