Go: Tratamento de erros e gerenciamento de pacotes no Go

Última atualização: 2026-08-26

Erros são valores, não exceções — o Go trata os erros como valores de retorno comuns, e esse design torna o tratamento de erros explícito, controlável e combinável.

A filosofia de tratamento de erros e o gerenciamento de pacotes do Go são os pilares de um código pronto para produção. Nesta lição, você vai dominar os dois recursos essenciais mais subestimados do Go.

1. Você aprenderá


2. A história real de um engenheiro de microsserviços

(1) Problema: O pânico online causou a falha do serviço, e erros 500 inundaram o grupo de alertas

Alice é engenheira de back-end na equipe de microsserviços. O serviço de usuários que ela mantém enfrentou recentemente um grande problema:

“O serviço ao usuário travou três vezes na semana passada, sempre devido a uma referência a um ponteiro nulo. Sempre que o serviço em Go entra em pânico, todo o processo é encerrado e nenhum usuário consegue fazer login — o gerente de produto disse que, se o serviço travar mais uma vez, os bônus serão reduzidos.”

Ela verificou o código de erro no local da falha:

GO
// Bad code: No error handling; it just panics.
func getUserByID(db *sql.DB, id int) *User {
    rows, _ := db.Query("SELECT * FROM users WHERE id = ?", id)
    // If the ID does not exist, rows.Next() returns false.
    // However, directly accessing the value below—the rows.Scan operation on nil—causes a panic.
    var user User
    for rows.Next() {
        rows.Scan(&user.Name, &user.Age)
    }
    return &user
}

Três problemas: (1) Os erros em db.Query são ignorados; (2) A existência do resultado não é verificada; (3) Um erro grave faz com que todo o processo trave.

(2) A solução do Go: erros são valores

GO
// user_service.go
package main

import (
    "errors"
    "fmt"
)

// Custom Error Types
type NotFoundError struct {
    ID int
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("user %d not found", e.ID)
}

// Error Sentinel
var ErrInvalidInput = errors.New("invalid input")

// Robust Query Function
func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("findUser: %w", ErrInvalidInput)
    }

    users := map[int]User{
        1: {Name: "Alice", Age: 28},
        2: {Name: "Bob", Age: 32},
    }

    user, ok := users[id]
    if !ok {
        return nil, NotFoundError{ID: id}
    }
    return &user, nil
}

type User struct {
    Name string
    Age  int
}

func main() {
    for _, id := range []int{1, -1, 999} {
        user, err := findUser(id)
        if err != nil {
            // Determining the Type of Error
            if errors.Is(err, ErrInvalidInput) {
                fmt.Printf("Input error (skipped): %v\n", err)
                continue
            }
            var nf NotFoundError
            if errors.As(err, &nf) {
                fmt.Printf("User %d does not exist\n", nf.ID)
                continue
            }
            fmt.Printf("Unknown error: %v\n", err)
            continue
        }
        fmt.Printf("Found: %s (%d)\n", user.Name, user.Age)
    }
}

Resultado:

TEXT 📖 Somente leitura
Found: Alice (28)
Input error (skipped): findUser: invalid input
User 999 does not exist

(3) Benefícios: Comparação do tratamento de erros

Dimensão Linguagem try-catch Erro em Go
O erro é Fluxo de controle de exceções Valor de retorno normal
Explícito Implícito (é fácil não perceber o bloco catch) Explícito if err != nil
Desempenho Sobrecarga de desdobramento da pilha Sem sobrecarga adicional
Componibilidade Ruim (interrompe o fluxo de forma anômala) Boa (o err pode ser passado livremente)
💡 Dica: Quando exceções são lançadas em Java, C++ ou Python, ocorre o desenrolamento da pilha; em Go, um error é simplesmente um valor de interface (16 bytes), portanto, passá-lo não envolve praticamente nenhuma sobrecarga.


3. Interface de erros

(1) O que é um erro?

GO
type error interface {
    Error() string
}

Qualquer tipo que implemente o método Error() string é considerado um erro.

(2) ▶ Exemplo: 4 maneiras de criar um erro

GO
package main

import (
    "errors"
    "fmt"
)

// Method 1: errors.New (most commonly used)
var ErrNotFound = errors.New("resource not found")

// Method 2: fmt.Errorf (with formatting)
func validate(age int) error {
    if age < 0 {
        return fmt.Errorf("invalid age: %d (must be >= 0)", age)
    }
    return nil
}

// Method 3: Wrap the error in fmt.Errorf (%w)
func loadConfig(path string) error {
    if path == "" {
        return fmt.Errorf("loadConfig: %w", ErrNotFound)
    }
    return nil
}

// Method 4: Customizing the error type
type TimeoutError struct {
    DurationMs int
    Operation  string
}

func (e TimeoutError) Error() string {
    return fmt.Sprintf("%s timed out after %dms", e.Operation, e.DurationMs)
}

func main() {
    // Method 1
    fmt.Println(ErrNotFound)  // resource not found

    // Method 2
    fmt.Println(validate(-5))  // invalid age: -5 (must be >= 0)

    // Method 3
    fmt.Println(loadConfig(""))  // loadConfig: resource not found

    // Method 4
    err := TimeoutError{DurationMs: 5000, Operation: "DB query"}
    fmt.Println(err)  // DB query timed out after 5000ms
}
▶ Experimente

(3) Comparação dos quatro métodos de criação

Método Função/Sintaxe Finalidade Suporta encadeamento de erros?
errors.New errors.New("msg") Erro estático simples
fmt.Errorf fmt.Errorf("msg %d", n) Erro formatado
fmt.Errorf(%w) fmt.Errorf("ctx: %w", err) Erro encapsulado ✅ errors.Is/As
Tipo personalizado struct { ... Error() string } Erro com campos adicionais ✅ Personalizado

4. erros.Is / erros.As — cadeia de erros

(1) errors.Is: Verifica se uma determinada sentinela está incluída na cadeia de erros

GO
package main

import (
    "errors"
    "fmt"
)

var ErrDB = errors.New("database error")
var ErrConn = fmt.Errorf("connection failed: %w", ErrDB)

func main() {
    err := fmt.Errorf("query failed: %w", ErrConn)

    // errors.Is searches layer by layer along the %w chain
    fmt.Println(errors.Is(err, ErrDB))    // true
    fmt.Println(errors.Is(err, ErrConn))  // true

    // == Can only match the outermost level
    fmt.Println(err == ErrDB)   // false (different objects)
    fmt.Println(err == ErrConn) // false
}

(2) ▶ Exemplo: errors.As: extrai tipos específicos de erros da cadeia

GO
package main

import (
    "errors"
    "fmt"
)

type ValidationError struct {
    Field string
    Value interface{}
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation failed: %s = %v", e.Field, e.Value)
}

func process(input string) error {
    if input == "" {
        return ValidationError{Field: "input", Value: ""}
    }
    return nil
}

func main() {
    err := process("")

    // errors.As: Extracts the ValidationError type from the chain
    var valErr ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("Field %s is invalid, value=%v\n", valErr.Field, valErr.Value)
    }

    // Also works with wrapping
    wrapped := fmt.Errorf("process failed: %w", err)
    var valErr2 ValidationError
    if errors.As(wrapped, &valErr2) {
        fmt.Printf("(After wrapping) Field %s is invalid\n", valErr2.Field)
    }
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
Field input is invalid, value=
(After wrapping) Field input is invalid

(3) “errors.Is” vs “errors.As”

Função Método de correspondência Finalidade
errors.Is(err, target) Igual a (==) Verifica se ocorreu um erro de sentinela específico
errors.As(err, &target) Correspondência de tipos Recuperar um erro de um tipo específico da cadeia de erros

5. pânico / recuperação

(1) panic: erro irrecuperável

GO
package main

import "fmt"

func main() {
    fmt.Println("Start")

    // A panic immediately terminates the current function and begins stack unwinding.
    panic("something went terribly wrong")

    // This line will not be executed
    fmt.Println("End")
}

Resultado:

TEXT 📖 Somente leitura
Start
panic: something went terribly wrong

goroutine 1 [running]:
main.main()
        /tmp/main.go:8 +0x...
exit status 2

(2) ▶ Exemplo: recuperar-se (recuperar-se de um pânico)

GO
package main

import (
    "fmt"
)

// recover is only useful in defer
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()

    // Intentionally triggering a panic
    if b == 0 {
        panic("division by zero")
    }
    return a / b, nil
}

func main() {
    // Normal call
    if r, err := safeDivide(10, 2); err == nil {
        fmt.Printf("10/2 = %d\n", r)
    }

    // A panic is caught by recover and does not cause a crash
    if r, err := safeDivide(10, 0); err != nil {
        fmt.Printf("Error: %v (result=%d)\n", err, r)
    }

    fmt.Println("Program ended normally—panic was recovered")
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
10/2 = 5
Error: panic recovered: division by zero (result=0)
Program ended normally—panic was recovered

(3) Casos de uso para “panic” versus “error”

Cenário Usar error Usar panic
Erro de entrada do usuário
O arquivo não existe
Tempo limite de rede
desreferência de ponteiro nulo ❌ (não pode ser recuperado) ✅ (erro de código)
Índice de matriz fora dos limites ❌ (Não verificado pelo compilador) ✅ (Erro no código)
Falha na inicialização (condição obrigatória)
🔥 Erro comum: panic + recover não deve ser usado para simular um bloco try-catch. A filosofia do Go é “use panic com moderação e use error com mais frequência”. panic só deve ser usado para condições excepcionalmente graves (erros de código, falhas de inicialização ou estados irrecuperáveis).


6. Gerenciamento de pacotes Go Mod

(1) Os três principais comandos no Go Mod

Comando Função Casos de uso comuns
go mod init <module> Inicializar um módulo Iniciar um novo projeto
go mod tidy Organizar as dependências (adicionar as que faltam, remover as desnecessárias) Após modificar as instruções de importação
go mod add <path>@<ver> Adicionar uma dependência (Novo no Go 1.22+) Para adicionar um pacote externo
go get <path>@<ver> Adicionar/Atualizar Dependências Método Tradicional

(2) ▶ Exemplo: Criar um módulo + Adicionar dependências

BASH
# 1. Initialize module
$ go mod init github.com/alice/user-service
go: creating new go.mod: module github.com/alice/user-service

# 2. Import an external package in the code
GO
package main

import (
    "fmt"
    "github.com/google/uuid"  // external dependency
)

func main() {
    id := uuid.New()
    fmt.Printf("Generated UUID: %s\n", id)
}
BASH
# 3. Add dependencies and organize
$ go mod tidy
go: finding module for package github.com/google/uuid
go: found github.com/google/uuid in github.com/google/uuid v1.6.0

# 4. View the generated go.mod
$ cat go.mod
module github.com/alice/user-service

go 1.22

require github.com/google/uuid v1.6.0

(3) Regras de exportação de pacotes

GO
// math/calculator.go
package math

// Uppercase = Public (accessible to other packages)
func Add(a, b int) int { return a + b }
var Version = "1.0"

// Lowercase first letter = private (visible only within the package)
func helper(x int) int { return x * 2 }
var internalVersion = "0.5"

// Public Structure
type Calculator struct {
    // Public field
    Name string
    // Private field (cannot be accessed directly from outside the package)
    precision int
}
GO
package main

import "yourmodule/math"

func main() {
    math.Add(1, 2)      // ✅ Public
    math.Version        // ✅ Public variable

    // math.helper(5)   // ❌ Private function; compilation error
    // math.internalVersion  // ❌ Private variable

    c := math.Calculator{Name: "basic"}  // ✅ Public struct
    // c.precision = 2  // ❌ Private field; compilation error
}

(4) ▶ Exemplo: Exportação de pacote + Passagem do tipo de erro

GO
// apperrors/errors.go
package apperrors

import "fmt"

// Public Error Type (Uppercase)
type BusinessError struct {
    Code    int
    Message string
}

func (e BusinessError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// Public Sentinel
var ErrUnauthorized = BusinessError{Code: 401, Message: "unauthorized"}

// Private error (external packages cannot reference directly)
type internalError struct {
    detail string
}

func (e internalError) Error() string {
    return fmt.Sprintf("internal: %s", e.detail)
}

// Public factory function (external packages use internalError indirectly through this function)
func NewInternalError(detail string) error {
    return internalError{detail: detail}
}
▶ Experimente

7. Exemplo completo: um serviço de usuário robusto

Integrando o tratamento de erros, o gerenciamento de pacotes e os erros personalizados:

GO
// user_service.go
package main

import (
    "errors"
    "fmt"
)

// ---------- Error Definitions ----------

type NotFoundError struct {
    Resource string
    ID       int
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("%s with id %d not found", e.Resource, e.ID)
}

type ValidationError struct {
    Field   string
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation failed: %s - %s", e.Field, e.Message)
}

type DBError struct {
    Operation string
    Err       error
}

func (e DBError) Error() string {
    return fmt.Sprintf("db %s failed: %v", e.Operation, e.Err)
}

func (e DBError) Unwrap() error {
    return e.Err
}

// Sentinel Error
var ErrInternal = errors.New("internal server error")

// ---------- Data Layer (Simulated DB) ----------

type User struct {
    ID   int
    Name string
    Age  int
}

func queryUserFromDB(id int) (*User, error) {
    db := map[int]User{
        1: {ID: 1, Name: "Alice", Age: 28},
        2: {ID: 2, Name: "Bob", Age: 32},
    }
    user, ok := db[id]
    if !ok {
        return nil, NotFoundError{Resource: "user", ID: id}
    }
    return &user, nil
}

// ---------- Service Layer ----------

func GetUser(id int) (*User, error) {
    // panic protection
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("[PANIC] recovered: %v\n", r)
        }
    }()

    if id <= 0 {
        return nil, ValidationError{
            Field:   "id",
            Message: "must be positive",
        }
    }

    user, err := queryUserFromDB(id)
    if err != nil {
        var nf NotFoundError
        if errors.As(err, &nf) {
            return nil, nf
        }
        return nil, DBError{
            Operation: "queryUserFromDB",
            Err:       err,
        }
    }

    if user.Age < 0 || user.Age > 150 {
        return nil, ValidationError{
            Field:   "age",
            Message: fmt.Sprintf("unexpected age: %d", user.Age),
        }
    }

    return user, nil
}

// ---------- HTTP Layer ----------

func HandleGetUser(id int) {
    user, err := GetUser(id)
    if err != nil {
        var nf NotFoundError
        var ve ValidationError
        var de DBError

        switch {
        case errors.As(err, &nf):
            fmt.Printf("[404] %v\n", err)
        case errors.As(err, &ve):
            fmt.Printf("[400] %v\n", err)
        case errors.As(err, &de):
            fmt.Printf("[500] db error: %v\n", de)
            fmt.Printf("[500] Internal: %+v\n", de.Err)
        default:
            fmt.Printf("[500] %v\n", err)
        }
        return
    }
    fmt.Printf("[200] User: %+v\n", user)
}

func main() {
    // Normal
    HandleGetUser(1)

    // Input error (ValidationError with additional information)
    HandleGetUser(0)

    // User does not exist (custom NotFoundError)
    HandleGetUser(999)

    fmt.Println("\n=== Program Exited Normally ===")
}

Resultado esperado:

TEXT 📖 Somente leitura
[200] User: &{ID:1 Name:Alice Age:28}
[400] validation failed: id - must be positive
[404] user with id 999 not found

=== Program Exited Normally ===
100%
flowchart TD
    A[Function returns error] --> B{err == nil?}
    B -->|Yes| C[Normal processing]
    B -->|No| D[Determine error type]
    D --> E[errors.Is / == sentinel]
    D --> F[errors.As / type assertion]
    D --> G[type switch]
    E --> H[Handle specific sentinel error]
    F --> I[Extract structured error info]
    G --> J[Branch by type]
    H --> K[Return or retry]
    I --> K
    J --> K
🔥 Erro comum: O método Unwrap() error na estrutura DBError é fundamental para permitir que erros personalizados participem da cadeia de erros. Se um tipo personalizado não tiver um método Unwrap(), errors.Is e errors.As verificarão apenas a camada mais externa.


❓ Perguntas Frequentes

P: Qual é o tipo de error? R: error é uma interface embutida: type error interface { Error() string }. Qualquer tipo que implemente o método Error() string é um error — um valor de interface de 16 bytes.

P: Como faço para personalizar erros? R: Defina uma estrutura e implemente o método Error() string. Se quiser oferecer suporte ao encadeamento de erros (traversal errors.Is/As), adicione um método Unwrap() error que retorne o erro interno.

P: É necessário usar recover depois de panic? R: Não necessariamente. recover só é útil dentro de um bloco defer e deve ser colocado apenas na entrada de uma goroutine (go func() { defer recover() }). Não use recover em sua lógica de negócios — isso mascara os bugs em vez de corrigi-los.

P: Qual é a diferença entre errors.Is e errors.As? R: errors.Is(err, target) realiza comparações de valores ao longo da cadeia %w, nível por nível (==); errors.As(err, &target) realiza verificações de tipo passo a passo ao longo da cadeia e preenche target. Em termos simples: Is verifica o valor, enquanto As extrai o tipo.

P: Como o go mod gerencia as dependências? R: Fluxo de trabalho principal: go mod init para inicializar → escrever código e usar importgo mod tidy para baixar e organizar automaticamente → bloquear versões usando go.mod e go.sum. O Go 1.22+ introduz o comando go mod add, que é mais intuitivo.

P: Quais são as regras para nomes públicos e privados? R: Há uma regra: nomes que começam com letra maiúscula são públicos (exportados), enquanto aqueles que começam com letra minúscula são privados. Isso se aplica a variáveis, funções, tipos, campos de estruturas e constantes. Não há public/private palavras-chave.

P: Qual é a diferença entre fmt.Errorf(%w) e fmt.Errorf(%v)? R: %w gera um erro com uma cadeia de erros que pode ser percorrida por errors.Is/As; %v simplesmente formata uma string e cria um novo erro que não tem relação com o erro original.

P: Como os erros podem ser tratados de maneira adequada no código de produção? R: (1) Aninhar fmt.Errorf("context: %w", err) para preservar a cadeia de erros; (2) Definir tipos de erros de negócios com campos adicionais; (3) Resolver os erros de maneira uniforme na camada do manipulador HTTP → códigos de status HTTP; (4) Registrar a cadeia completa (%+v).


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Defina uma função Divide func Divide(a, b float64) (float64, error) que retorne errors.New("division by zero") se o divisor for 0 e, caso contrário, retorne o quociente.

  2. Problema avançado (Dificuldade ⭐⭐): Implemente um ConfigLoader que suporte o carregamento da configuração a partir de um arquivo JSON e, caso isso não seja possível, recorra às variáveis de ambiente. Requisitos: Envolva cada nível de erro com fmt.Errorf(%w) e permita que o chamador use errors.Is para determinar se o erro é “arquivo não encontrado” ou “erro de análise de JSON”.

  3. Problema de desafio (Dificuldade ⭐⭐⭐): Construa uma arquitetura de tratamento de erros em três camadas: (1) Camada de dados Repository → Retorna NotFoundError / DBError; (2) Camada de serviço Service → Envolve os erros da camada de dados + adiciona ValidationError; (3) Manipulador HTTP → Analise os erros camada por camada usando errors.As e mapeie-os para códigos de status HTTP (404/400/500). A estrutura do erro deve incluir campos de negócios (ID/Campo/Operação).

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%