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á
- A interface
errore quatro maneiras de criá-la errors.Is/errors.Aspara verificar a cadeia de erros- Tipos de erro personalizados
- Casos de uso para
panic/recover go modgerenciamento de dependências (init/tidy/add)- Regras de exportação de pacotes (letras maiúsculas = público, letras minúsculas = privado)
- Criação de serviços para usuários com tratamento robusto de erros
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:
// 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
// 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:
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) |
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?
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
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
}
(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
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
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)
}
}
Resultado:
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
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:
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)
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")
}
Resultado:
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) | ❌ | ✅ |
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
# 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
package main
import (
"fmt"
"github.com/google/uuid" // external dependency
)
func main() {
id := uuid.New()
fmt.Printf("Generated UUID: %s\n", id)
}
# 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
// 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
}
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
// 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}
}
7. Exemplo completo: um serviço de usuário robusto
Integrando o tratamento de erros, o gerenciamento de pacotes e os erros personalizados:
// 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:
[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 ===
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
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étodoError() stringé umerror— 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 (traversalerrors.Is/As), adicione um métodoUnwrap() errorque retorne o erro interno.
P: É necessário usar
recoverdepois depanic? R: Não necessariamente.recoversó é útil dentro de um blocodefere deve ser colocado apenas na entrada de uma goroutine (go func() { defer recover() }). Não userecoverem sua lógica de negócios — isso mascara os bugs em vez de corrigi-los.
P: Qual é a diferença entre
errors.Iseerrors.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 preenchetarget. Em termos simples:Isverifica o valor, enquantoAsextrai o tipo.
P: Como o
go modgerencia as dependências? R: Fluxo de trabalho principal:go mod initpara inicializar → escrever código e usarimport→go mod tidypara baixar e organizar automaticamente → bloquear versões usandogo.modego.sum. O Go 1.22+ introduz o comandogo 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/privatepalavras-chave.
P: Qual é a diferença entre
fmt.Errorf(%w)efmt.Errorf(%v)? R:%wgera um erro com uma cadeia de erros que pode ser percorrida porerrors.Is/As;%vsimplesmente 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
erroré uma interface embutida; qualquer tipo que implementeError() stringé umerror- 4 maneiras de criar erros:
errors.New/fmt.Errorf/%wencapsulamento / tipos personalizados errors.Isverifica se o alvo está incluído na cadeia de erros (correspondência de valores)errors.Asextrai erros de um tipo específico da cadeia de erros (com base na correspondência de tipos)panicé usado para erros irrecuperáveis;recoversó é válido dentro de um blocodefergo mod init/tidy/addpara gerenciar dependências externas- Há apenas uma regra para a exportação de pacotes: letras maiúsculas = público, letras minúsculas = privado
- Código de produção: erros aninhados + tipos de erro personalizados + tratamento unificado
📝 Exercícios
-
Problema básico (Dificuldade ⭐): Defina uma função
Dividefunc Divide(a, b float64) (float64, error)que retorneerrors.New("division by zero")se o divisor for 0 e, caso contrário, retorne o quociente. -
Problema avançado (Dificuldade ⭐⭐): Implemente um
ConfigLoaderque 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 comfmt.Errorf(%w)e permita que o chamador useerrors.Ispara determinar se o erro é “arquivo não encontrado” ou “erro de análise de JSON”. -
Problema de desafio (Dificuldade ⭐⭐⭐): Construa uma arquitetura de tratamento de erros em três camadas: (1) Camada de dados
Repository→ RetornaNotFoundError/DBError; (2) Camada de serviçoService→ Envolve os erros da camada de dados + adicionaValidationError; (3) Manipulador HTTP → Analise os erros camada por camada usandoerrors.Ase 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).