Em Go, todo arquivo fonte pertence a um pacote. Não existe código solto fora de um pacote — a declaração package nome na primeira linha de cada arquivo é obrigatória. Pacotes são simultaneamente a unidade de compilação, a unidade de encapsulamento e a unidade de reutilização da linguagem.
Entender como pacotes funcionam é compreender como Go organiza, expõe e protege código. Um projeto bem estruturado em pacotes é mais fácil de testar, mais fácil de manter e mais fácil de evoluir do que um projeto onde tudo está acumulado em um único arquivo ou pacote.
Visibilidade: exportado vs não exportado
Go usa uma regra simples e elegante para controlar visibilidade: a primeira letra do identificador determina se ele é público ou privado.
Identificadores que começam com letra maiúscula são exportados — acessíveis por qualquer pacote que importe o pacote onde foram definidos. Identificadores que começam com letra minúscula são não exportados — acessíveis apenas dentro do próprio pacote.
Essa regra se aplica a tudo: funções, tipos, variáveis, constantes, campos de struct e métodos.
package financeiro
import "fmt"
// Exportados — visíveis fora do pacote
type Conta struct {
Titular string // exportado
saldo float64 // não exportado — privado ao pacote
}
const TaxaJuros = 0.015 // exportado
// Exportado
func NovaConta(titular string, deposito float64) *Conta {
return &Conta{
Titular: titular,
saldo: deposito,
}
}
// Exportado
func (c *Conta) Depositar(valor float64) error {
if valor <= 0 {
return fmt.Errorf("valor inválido: %.2f", valor)
}
c.saldo += valor
return nil
}
// Exportado
func (c *Conta) Saldo() float64 {
return c.saldo
}
// Não exportado — uso interno do pacote
func (c *Conta) aplicarTaxa() {
c.saldo -= c.saldo * TaxaJuros
}
Código em outro pacote que importar financeiro poderá usar NovaConta, Depositar, Saldo e TaxaJuros, mas não poderá acessar saldo diretamente nem chamar aplicarTaxa. Esse encapsulamento protege invariantes internas e permite refatorar a implementação sem quebrar o código que usa o pacote.
Nomenclatura de pacotes
A comunidade Go consolidou convenções fortes para nomear pacotes. Seguí-las torna o código instantaneamente reconhecível como Go idiomático.
Nomes curtos, minúsculos e sem underscores ou camelCase:
// Correto
package http
package json
package usuario
package pedido
// Evite
package HttpClient
package user_service
package gestãoDePedidos
O nome do pacote deve descrever o que ele fornece, não o que ele faz:
// Bom — descreve o que é
package crypto
package parser
package cache
// Evite — genérico demais
package util
package helpers
package common
package misc
Nomes como util e helpers são sintomas de um pacote que cresceu sem propósito claro. Se um pacote se chama util, é provável que ele deveria ser dividido em pacotes menores com nomes mais específicos.
O nome do pacote é parte da API. Quando um usuário chama json.Marshal, o nome json faz parte da expressão. Por isso, evite redundância entre o nome do pacote e os identificadores que ele exporta:
// Ruim — redundante: usuario.UsuarioService, usuario.UsuarioID
package usuario
type UsuarioService struct{}
type UsuarioID int
// Bom — natural: usuario.Service, usuario.ID
package usuario
type Service struct{}
type ID int
Importando pacotes
A declaração import traz pacotes para o escopo do arquivo. A forma padrão usa o caminho do módulo:
import (
"fmt"
"net/http"
"encoding/json"
"github.com/gin-gonic/gin"
"meu-projeto/internal/usuario"
)
Alias de importação. Quando dois pacotes têm o mesmo nome, ou quando o nome padrão é inconveniente, um alias resolve a ambiguidade:
import (
"crypto/rand"
mrand "math/rand" // alias para distinguir dos dois "rand"
)
func main() {
// rand do crypto
b := make([]byte, 16)
rand.Read(b)
// rand do math
n := mrand.Intn(100)
fmt.Println(n)
}
Importação em branco. Às vezes um pacote precisa ser importado apenas pelos efeitos colaterais de seu init — por exemplo, registrar um driver de banco de dados — sem usar nenhum identificador exportado. O _ evita o erro de importação não usada:
import (
"database/sql"
_ "github.com/lib/pq" // registra o driver PostgreSQL via init()
)
Importação com ponto. Importa todos os identificadores exportados diretamente no escopo atual, sem prefixo. Raramente usada em código de produção, mais comum em testes:
import . "fmt"
func main() {
Println("sem prefixo fmt") // em vez de fmt.Println
}
O diretório internal
Go possui um mecanismo de visibilidade a nível de diretório chamado internal. Pacotes dentro de um diretório internal só podem ser importados por código dentro da árvore de diretórios pai do internal:
meu-projeto/
├── go.mod
├── main.go
├── internal/
│ ├── banco/
│ │ └── banco.go ← só importável dentro de meu-projeto/
│ └── config/
│ └── config.go ← só importável dentro de meu-projeto/
└── pkg/
└── cliente/
└── cliente.go ← importável por qualquer projeto externo
Se outro projeto tentar importar meu-projeto/internal/banco, o compilador recusará com erro. Esse mecanismo é poderoso para criar APIs internas sem expô-las publicamente — a alternativa ao internal seria deixar o código acessível e confiar apenas em documentação para indicar que não deve ser usado externamente.
Estruturas de projeto recomendadas
Go não impõe uma estrutura de diretórios obrigatória além da localização do go.mod. A comunidade converge em torno de algumas convenções práticas.
Projeto simples — utilitário ou microsserviço:
meu-servico/
├── go.mod
├── go.sum
├── main.go
├── handler/
│ └── handler.go
├── service/
│ └── service.go
└── repository/
└── repository.go
Projeto com múltiplos binários:
meu-projeto/
├── go.mod
├── go.sum
├── cmd/
│ ├── api/
│ │ └── main.go ← binário da API
│ └── worker/
│ └── main.go ← binário do worker
├── internal/
│ ├── config/
│ ├── database/
│ └── domain/
└── pkg/
└── middleware/
O diretório cmd/ contém os pontos de entrada (main.go) de cada binário. Toda a lógica real fica em internal/ ou pkg/, mantendo os main.go enxutos — idealmente com apenas a montagem das dependências e a chamada de inicialização.
Projeto com domínios complexos — inspirado em Clean Architecture:
meu-projeto/
├── go.mod
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── domain/
│ │ ├── usuario.go
│ │ └── pedido.go
│ ├── usecase/
│ │ ├── criar_usuario.go
│ │ └── processar_pedido.go
│ ├── repository/
│ │ ├── usuario_repo.go
│ │ └── pedido_repo.go
│ └── handler/
│ ├── usuario_handler.go
│ └── pedido_handler.go
└── pkg/
├── validator/
└── logger/
Inicialização de pacotes com init
Como visto no artigo sobre funções, cada pacote pode ter funções init que executam antes de main. A ordem de execução é determinada pela ordem de importação: os pacotes importados são inicializados antes do pacote que os importa.
// pacote config
package config
import "fmt"
var Ambiente string
func init() {
Ambiente = "desenvolvimento"
fmt.Println("config: inicializado")
}
// pacote main
package main
import (
"fmt"
"meu-projeto/internal/config"
)
func init() {
fmt.Println("main: init executado, ambiente:", config.Ambiente)
}
func main() {
fmt.Println("main: executando")
}
// Saída:
// config: inicializado
// main: init executado, ambiente: desenvolvimento
// main: executando
Pacotes e testabilidade
Uma consequência direta de uma boa estrutura de pacotes é a facilidade de testar. Pacotes com responsabilidades claras e bem delimitadas têm interfaces menores, dependências explícitas e são mais fáceis de isolar em testes.
O padrão mais comum é colocar os testes no mesmo pacote com o sufixo _test:
// arquivo: financeiro/conta_test.go
package financeiro_test // teste externo — acessa apenas a API pública
import (
"testing"
"meu-projeto/internal/financeiro"
)
func TestDepositar(t *testing.T) {
conta := financeiro.NovaConta("Ricardo", 100.0)
if err := conta.Depositar(50.0); err != nil {
t.Fatalf("erro inesperado: %v", err)
}
if conta.Saldo() != 150.0 {
t.Errorf("esperado 150.0, obtido %.2f", conta.Saldo())
}
}
Usar package financeiro_test em vez de package financeiro força o teste a interagir apenas com a API pública — o que é uma verificação valiosa de que a API pública é suficiente e bem desenhada.
Documentação de pacotes
Todo pacote exportado deve ter um comentário de documentação começando com Package nome:
// Package financeiro fornece tipos e operações para gestão de contas bancárias,
// incluindo depósitos, saques e aplicação de taxas de juros.
package financeiro
Funções e tipos exportados devem ter comentários que começam com o nome do identificador:
// NovaConta cria e retorna uma nova conta bancária para o titular informado,
// com o depósito inicial fornecido. Retorna erro se o depósito for negativo.
func NovaConta(titular string, deposito float64) (*Conta, error) {
Esses comentários são exibidos pelo go doc e pelo site pkg.go.dev automaticamente, formando a documentação oficial do pacote.
Resumo do que foi coberto
Este artigo apresentou o sistema de pacotes do Go em profundidade: a regra de visibilidade por letra maiúscula, convenções de nomenclatura, formas de importação, o diretório internal para encapsulamento estrutural, estruturas de projeto recomendadas para diferentes escalas, a ordem de inicialização com init, a relação entre pacotes e testabilidade e a documentação de pacotes com comentários. O próximo artigo aprofunda o sistema de módulos — a camada que gerencia dependências entre pacotes.
Referências e leituras complementares
-
Effective Go — Package names — Convenções oficiais de nomenclatura de pacotes. https://go.dev/doc/effective_go#package-names
-
Go Blog: Organizing a Go module — Guia oficial para estruturar projetos Go. https://go.dev/doc/modules/layout
-
Go Blog: Package names — Artigo detalhado sobre como nomear pacotes idiomaticamente. https://go.dev/blog/package-names
-
Standard Go Project Layout — Referência comunitária amplamente adotada para estrutura de projetos. https://github.com/golang-standards/project-layout
-
Documentação sobre internal packages — Regras oficiais do mecanismo internal. https://pkg.go.dev/cmd/go#hdr-Internal_Directories
-
go doc — Documentação de linha de comando — Como usar o comando go doc para explorar pacotes. https://pkg.go.dev/cmd/doc