<h2>O Mecanismo de Erros em Go</h2>
<p>Go adota uma abordagem pragmática para tratamento de erros, diferente de linguagens que utilizam exceções (como Java ou Python). Em vez de usar try-catch ou mecanismos de exceção, Go trata erros como <strong>valores</strong>. Isso significa que você passa erros como retorno de função, exatamente como faria com qualquer outro valor. Essa filosofia simplifica o raciocínio sobre o fluxo do programa, pois o tratamento de erro é explícito e visível no código.</p>
<p>A base desse sistema é a <strong>interface <code>error</code></strong>, um tipo simples mas poderoso. Qualquer tipo que implementa o método <code>Error() string</code> satisfaz essa interface. Vejamos como funciona na prática:</p>
<pre><code class="language-go">package main
import (
"fmt"
)
// Implementando a interface error manualmente
type MeuErro struct {
mensagem string
codigo int
}
func (e MeuErro) Error() string {
return fmt.Sprintf("Erro [%d]: %s", e.codigo, e.mensagem)
}
func conectarBancoDados(host string) (string, error) {
if host == "" {
return "", MeuErro{
mensagem: "Host não pode estar vazio",
codigo: 400,
}
}
return "Conexão estabelecida", nil
}
func main() {
resultado, err := conectarBancoDados("")
if err != nil {
fmt.Println(err) // Output: Erro [400]: Host não pode estar vazio
return
}
fmt.Println(resultado)
}</code></pre>
<p>O padrão acima mostra que qualquer tipo pode ser um erro em Go. Não há herança ou classe base mágica — apenas o contrato da interface. Isso oferece flexibilidade e clareza: você sabe exatamente o que pode dar errado em uma função porque ela declara seu retorno como <code>error</code>.</p>
<h2>Sentinel Errors: Comparações por Identidade</h2>
<p>Sentinel errors são <strong>valores de erro específicos</strong> que você define para representar situações bem conhecidas e esperadas. O termo "sentinel" refere-se a um valor especial usado como marcador. Em Go, você cria constants ou variables de erro e as compara por identidade usando <code>==</code>. Esse padrão é extremamente útil quando você quer que o chamador distingua entre diferentes tipos de falha.</p>
<p>A biblioteca padrão de Go usa este padrão extensivamente. Considere <code>io.EOF</code> — é um valor de erro predefinido que indica fim de arquivo. Você não precisa verificar se a mensagem contém "EOF"; você compara diretamente:</p>
<pre><code class="language-go">package main
import (
"errors"
"fmt"
)
// Definindo sentinel errors no escopo do pacote
var (
ErrUsuarioNaoEncontrado = errors.New("usuário não encontrado")
ErrSenhaInvalida = errors.New("senha inválida")
ErrBancoIndisponivel = errors.New("banco de dados indisponível")
)
func autenticar(username, senha string) error {
if username == "" {
return ErrUsuarioNaoEncontrado
}
if senha != "correctPassword123" {
return ErrSenhaInvalida
}
return nil
}
func main() {
// Comparação por identidade
err := autenticar("joao", "senhaErrada")
if err == ErrSenhaInvalida {
fmt.Println("Acesso negado: credenciais incorretas")
} else if err == ErrUsuarioNaoEncontrado {
fmt.Println("Acesso negado: usuário inexistente")
} else if err == ErrBancoIndisponivel {
fmt.Println("Tente novamente mais tarde")
}
}</code></pre>
<p>O grande benefício dos sentinel errors é a <strong>performance e clareza</strong>. Você não precisa fazer parsing de strings de erro ou usar reflexão — é uma comparação de ponteiro rápida. No entanto, há uma limitação importante: sentinel errors não carregam contexto. Se você precisar adicionar informações dinâmicas (como qual recurso não foi encontrado, ou qual foi o timeout), você precisará avançar para tipos de erro customizados.</p>
<h2>Erros Customizados: Estruturas com Contexto</h2>
<p>Quando sentinel errors não são suficientes, você cria tipos de erro que implementam a interface <code>error</code> e carregam informações adicionais. Erros customizados permitem que você forneça contexto rico sobre o que deu errado, facilitando debugging e tratamento mais sofisticado.</p>
<h3>Estrutura Básica de um Erro Customizado</h3>
<pre><code class="language-go">package main
import (
"fmt"
"time"
)
// Erro customizado que carrega informações úteis
type ErroConexao struct {
Host string
Porta int
Tentativas int
UltimaConexao time.Time
MensagemOriginal error
}
func (e *ErroConexao) Error() string {
return fmt.Sprintf(
"Falha ao conectar em %s:%d após %d tentativas. Última tentativa: %s. Erro original: %v",
e.Host, e.Porta, e.Tentativas, e.UltimaConexao.Format(time.RFC3339), e.MensagemOriginal,
)
}
// Método auxiliar para extrair informações específicas
func (e *ErroConexao) Recuperavel() bool {
// Erros de conexão geralmente são recuperáveis
return true
}
func conectarComRetentativa(host string, porta int) error {
for tentativa := 1; tentativa <= 3; tentativa++ {
// Simulando falha
if tentativa < 3 {
continue
}
}
return &ErroConexao{
Host: host,
Porta: porta,
Tentativas: 3,
UltimaConexao: time.Now(),
MensagemOriginal: fmt.Errorf("connection timeout"),
}
}
func main() {
err := conectarComRetentativa("database.example.com", 5432)
// Extração de informações usando type assertion
if erroConn, ok := err.(*ErroConexao); ok {
fmt.Println("Erro de conexão detectado")
fmt.Println("Host:", erroConn.Host)
fmt.Println("É recuperável?", erroConn.Recuperavel())
fmt.Println("Mensagem:", err)
}
}</code></pre>
<h3>Validação e Erros Customizados</h3>
<p>Um caso muito comum é validação de dados. Você pode criar tipos de erro que armazenam múltiplos erros de validação:</p>
<pre><code class="language-go">package main
import (
"fmt"
"strings"
)
type ErroValidacao struct {
Campo string
Motivo string
}
func (e ErroValidacao) Error() string {
return fmt.Sprintf("Campo '%s' inválido: %s", e.Campo, e.Motivo)
}
// Agregar múltiplos erros
type ErroValidacaoMultipla struct {
Erros []ErroValidacao
}
func (e ErroValidacaoMultipla) Error() string {
if len(e.Erros) == 0 {
return "Nenhum erro de validação"
}
mensagens := make([]string, len(e.Erros))
for i, err := range e.Erros {
mensagens[i] = err.Error()
}
return strings.Join(mensagens, "; ")
}
type Usuario struct {
Nome string
Email string
Idade int
}
func validarUsuario(u Usuario) error {
var erros []ErroValidacao
if u.Nome == "" {
erros = append(erros, ErroValidacao{"Nome", "não pode estar vazio"})
}
if !strings.Contains(u.Email, "@") {
erros = append(erros, ErroValidacao{"Email", "formato inválido"})
}
if u.Idade < 18 {
erros = append(erros, ErroValidacao{"Idade", "deve ser maior que 18"})
}
if len(erros) > 0 {
return ErroValidacaoMultipla{Erros: erros}
}
return nil
}
func main() {
user := Usuario{
Nome: "",
Email: "email-invalido",
Idade: 15,
}
if err := validarUsuario(user); err != nil {
fmt.Println(err)
}
// Type assertion para acessar todos os erros
if multi, ok := err.(ErroValidacaoMultipla); ok {
fmt.Println("Quantidade de erros:", len(multi.Erros))
for _, e := range multi.Erros {
fmt.Printf("- %s\n", e)
}
}
}</code></pre>
<h2>Práticas Recomendadas e Padrões Avançados</h2>
<h3>Wrapping de Erros com Context</h3>
<p>A partir do Go 1.13, você pode usar <code>fmt.Errorf</code> com o verbo <code>%w</code> para envolver erros mantendo a cadeia de erro. Isso permite que ferramentas como <code>errors.Is()</code> e <code>errors.As()</code> funcionem corretamente:</p>
<pre><code class="language-go">package main
import (
"errors"
"fmt"
)
var ErrBancoDados = errors.New("erro no banco de dados")
func buscarUsuario(id int) (string, error) {
// Simulando erro no banco de dados
if id < 0 {
// Envolvendo o erro com contexto adicional
return "", fmt.Errorf("falha ao buscar usuário com ID %d: %w", id, ErrBancoDados)
}
return "João Silva", nil
}
func processarPedido(userID int) error {
usuario, err := buscarUsuario(userID)
if err != nil {
// Adicionando mais camadas de contexto
return fmt.Errorf("não foi possível processar pedido: %w", err)
}
fmt.Printf("Processando pedido para: %s\n", usuario)
return nil
}
func main() {
err := processarPedido(-1)
// Verificar se o erro original está na cadeia
if errors.Is(err, ErrBancoDados) {
fmt.Println("Problema no banco de dados detectado na cadeia de erro")
}
// Extrair o erro específico
var erroDB error
if errors.As(err, &erroDB) {
fmt.Printf("Erro extraído: %v\n", erroDB)
}
fmt.Println("Erro completo:", err)
}</code></pre>
<h3>Padrão de Erro com Tipo Específico</h3>
<p>Para casos onde você quer permitir que o chamador tome ações específicas, você pode criar tipos de erro que implementam interfaces adicionais:</p>
<pre><code class="language-go">package main
import (
"fmt"
"time"
)
// Interface para erros que podem ser temporários
type TemporariamenteIndisponivel interface {
error
Temporario() bool
Apos() time.Time
}
type ErroTaxa struct {
mensagem string
tentarApos time.Time
}
func (e ErroTaxa) Error() string {
return fmt.Sprintf("%s. Tente novamente após %s", e.mensagem, e.tentarApos.Format(time.RFC3339))
}
func (e ErroTaxa) Temporario() bool {
return true
}
func (e ErroTaxa) Apos() time.Time {
return e.tentarApos
}
func fazerRequisicao() error {
return ErroTaxa{
mensagem: "Taxa de requisições excedida",
tentarApos: time.Now().Add(5 * time.Minute),
}
}
func executarComRetentativa(fn func() error) {
err := fn()
if err != nil {
// Verificar se é temporário
if temp, ok := err.(TemporariamenteIndisponivel); ok && temp.Temporario() {
fmt.Printf("Erro temporário. Aguarde até %s antes de tentar novamente\n", temp.Apos())
} else {
fmt.Printf("Erro permanente: %v\n", err)
}
}
}
func main() {
executarComRetentativa(fazerRequisicao)
}</code></pre>
<h2>Conclusão</h2>
<p>Dominar o sistema de erros em Go significa entender três conceitos distintos e quando usar cada um. <strong>Primeiro, a interface <code>error</code></strong> é a base — qualquer tipo que implemente <code>Error() string</code> é um erro válido, oferecendo flexibilidade máxima. <strong>Segundo, sentinel errors</strong> são a escolha para situações bem definidas quando você só precisa sinalizar "qual tipo de erro", sem contexto adicional — use <code>==</code> para compará-los com eficiência. <strong>Terceiro, erros customizados</strong> são quando você precisa carregar contexto e informações dinâmicas — crie structs que implementem <code>error</code> e potencialmente outras interfaces para fornecer comportamentos específicos.</p>
<p>A prática de usar <code>fmt.Errorf</code> com <code>%w</code> para envolver erros (Go 1.13+) é fundamental para código moderno, permitindo que bibliotecas como <code>errors.Is()</code> funcionem corretamente e facilitando debugging em camadas profundas da aplicação.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://golang.org/doc/effective_go#errors" target="_blank" rel="noopener noreferrer">Effective Go - Error handling</a></li>
<li><a href="https://go.dev/blog/error-handling-and-go" target="_blank" rel="noopener noreferrer">Go Blog - Error Handling and Go</a></li>
<li><a href="https://go.dev/blog/go1.13-errors" target="_blank" rel="noopener noreferrer">Working with Errors in Go 1.13</a></li>
<li><a href="https://gobyexample.com/errors" target="_blank" rel="noopener noreferrer">Go by Example - Errors</a></li>
<li><a href="https://pkg.go.dev/errors" target="_blank" rel="noopener noreferrer">Standard Library - errors Package</a></li>
</ul>
<p><!-- FIM --></p>