Go: Referência de Eficiência Idiomática
Índice
- Tratamento de Erros
- Slices & Maps
- Goroutines & Canais
- Structs & Interfaces
- Funções & Closures
- Antipadrões específicos do Go
1. Tratamento de Erros {#errors}
// ❌ Ignorando erros
resultado, _ := os.Open(caminho)
// ✅ — sempre trate; use _ apenas quando o erro for comprovadamente irrelevante
resultado, err := os.Open(caminho)
if err != nil {
return fmt.Errorf("abrir %s: %w", caminho, err)
}
// ❌ Variável de erro redundante
err := fazA()
if err != nil { return err }
err = fazB()
if err != nil { return err }
// ✅ — cada :=/: é aceitável; isso é Go idiomático. Não tente "corrigir".
// O que você PODE simplificar: colapsar em uma linha quando o corpo do if é um único return
if err := fazA(); err != nil { return err }
if err := fazB(); err != nil { return err }
// ❌ Tipo de erro personalizado sem valor agregado
type MeuErro struct{ msg string }
func (e MeuErro) Error() string { return e.msg }
// ✅ — use errors.New ou fmt.Errorf a menos que os chamadores precisem inspecionar o tipo
var ErrNaoEncontrado = errors.New("não encontrado")
return fmt.Errorf("consulta %q: %w", chave, ErrNaoEncontrado)
Empacote erros com %w (não %v) para que os chamadores possam usar errors.Is / errors.As.
2. Slices & Maps {#slices}
// ❌ Aumentando um slice sem pré-alocação quando o tamanho é conhecido
var resultado []string
for _, item := range items {
resultado = append(resultado, item.Nome)
}
// ✅
resultado := make([]string, 0, len(items))
for _, item := range items {
resultado = append(resultado, item.Nome)
}
// ❌ Verificação manual de existência antes de escrever no map
if _, ok := m[chave]; !ok {
m[chave] = []string{}
}
m[chave] = append(m[chave], valor)
// ✅ — append em slice nil é Go válido
m[chave] = append(m[chave], valor)
// ❌ Copiando um map por atribuição (copia referência)
copia := original
// ✅
copia := make(map[K]V, len(original))
for k, v := range original { copia[k] = v }
3. Goroutines & Canais {#concurrency}
// ❌ Goroutine "dispara e esquece" sem ciclo de vida
vá trabalhar()
// ✅ — use errgroup ou WaitGroup para rastrear conclusão
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
trabalhar()
}()
wg.Wait()
// ❌ Canal sem buffer causando bloqueio desnecessário da goroutine
ch := make(chan Resultado)
go func() { ch <- computar() }()
resultado := <-ch
// ✅ — para resultado único, canal com buffer evita vazamento de goroutine se o receptor sair cedo
ch := make(chan Resultado, 1)
go func() { ch <- computar() }()
resultado := <-ch
// ❌ select com default de espera ocupada
for {
select {
case v := <-ch:
processar(v)
default:
// girar
}
}
// ✅ — select bloqueante a menos que você realmente precise de não bloqueio
for v := range ch {
processar(v)
}
Use golang.org/x/sync/errgroup para fan-out com coleta de erros.
4. Structs & Interfaces {#structs}
// ❌ Interface grande
type Armazenamento interface {
Get(chave string) ([]byte, error)
Set(chave string, val []byte) error
Delete(chave string) error
List(prefixo string) ([]string, error)
// ... mais 10 métodos
}
// ✅ — interfaces pequenas e compostas
type Obter interface { Get(chave string) ([]byte, error) }
type Definir interface { Set(chave string, val []byte) error }
type Armazenamento interface { Obter; Definir }
// ❌ Retornando struct concreto do construtor (amarra os chamadores à implementação)
func NovoArmazenamento() *ArmazenamentoRedis { ... }
// ✅ — retorne interface quando você tem ou antecipa múltiplas implementações
func NovoArmazenamento() Armazenamento { return &ArmazenamentoRedis{...} }
// ❌ Receiver ponteiro para tipos de valor pequenos
func (p *Ponto) X() float64 { return p.x }
// ✅ — receiver valor para tipos imutáveis pequenos
func (p Ponto) X() float64 { return p.x }
Regra: receiver ponteiro quando o método altera o estado OU a struct é grande (>3 campos de tamanho não trivial). Receiver valor caso contrário.
5. Funções & Closures {#functions}
// ❌ Valores de retorno nomeados usados apenas para evitar uma declaração de variável
func dividir(a, b float64) (resultado float64, err error) {
resultado = a / b
return
}
// ✅ — retornos nomeados valem a pena apenas para mutação adiada ou documentação
func dividir(a, b float64) (float64, error) {
if b == 0 { return 0, errors.New("divisão por zero") }
return a / b, nil
}
// ❌ Closure capturando variável de loop (erro clássico do Go, corrigido no Go 1.22+)
// Pré-1.22: cada goroutine captura o mesmo i
for i := 0; i < n; i++ {
go func() { usar(i) }()
}
// ✅ (Go <1.22 — passar como parâmetro)
for i := 0; i < n; i++ {
go func(i int) { usar(i) }(i)
}
// Go 1.22+: variável de loop com escopo por iteração, então o original é seguro
6. Antipadrões específicos do Go {#antipatterns}
| Antipadrão | Preferido |
|---|---|
if err != nil { return err } repetido 5+ vezes | aceitável — é Go idiomático |
panic para erros esperados | return err |
init() com efeitos colaterais | inicialização explícita em main ou construtores |
interface{} / any sem genéricos | use genéricos (Go 1.18+) ou interfaces tipadas |
| Campo Mutex não adjacente aos dados que protege | coloque mu diretamente acima do campo que ele protege |
| Canal de canais | geralmente sinal de superengenharia; redesenhe |
time.Sleep em testes | use hooks de testing ou canais para sincronização |
| Tipos exportados com campos não exportados (quando os campos são o ponto principal) | structs estilo record com todos os campos exportados |
log.Fatal fora de main | retorne erros para cima na pilha |
Limitações
- Estas são diretrizes específicas da linguagem e não cobrem decisões arquiteturais gerais.
- A supercompressão pode reduzir a legibilidade; use o bom senso.