Em Go, documentação não é um arquivo separado nem uma ferramenta externa — é o próprio código comentado. O godoc extrai comentários diretamente do código fonte e os transforma em documentação navegável, disponível tanto na linha de comando quanto no navegador.
Essa abordagem tem uma consequência poderosa: documentação que não está no código não existe para o godoc. Isso incentiva manter a documentação próxima do que ela descreve — reduzindo a probabilidade de ficar desatualizada.
As regras do godoc
O godoc segue convenções simples e estritas. Comentários de documentação são aqueles imediatamente antes de uma declaração, sem linha em branco entre o comentário e o identificador:
// Package financeiro fornece tipos e operações para gestão financeira,
// incluindo contas bancárias, transferências e relatórios de movimentação.
//
// Uso básico:
//
// conta, err := financeiro.NovaConta("Ricardo", 1000.00)
// if err != nil {
// log.Fatal(err)
// }
// conta.Depositar(500.00)
//
// Para mais exemplos, consulte os arquivos _test.go do pacote.
package financeiro
Regra 1: o comentário começa com o nome do identificador. Isso permite que o godoc gere índices e o texto faça sentido quando lido isoladamente.
Regra 2: sem linha em branco entre o comentário e a declaração. Uma linha em branco quebra a associação — o godoc não vai reconhecer o comentário como documentação daquele identificador.
Regra 3: use frases completas. O primeiro parágrafo é exibido nos índices como sumário.
Documentando funções e métodos
// NovaConta cria e retorna uma nova conta bancária para o titular informado.
// O depósito inicial deve ser maior ou igual a zero; valores negativos retornam erro.
//
// O titular é obrigatório — uma string vazia retorna ErrTitularVazio.
//
// Exemplo:
//
// conta, err := NovaConta("Ana Silva", 500.00)
// if err != nil {
// // tratar erro
// }
func NovaConta(titular string, depositoInicial float64) (*Conta, error) {
if titular == "" {
return nil, ErrTitularVazio
}
if depositoInicial < 0 {
return nil, fmt.Errorf("depósito inicial não pode ser negativo: %.2f", depositoInicial)
}
return &Conta{
titular: titular,
saldo: depositoInicial,
}, nil
}
// Depositar adiciona o valor informado ao saldo da conta.
// Retorna ErrValorInvalido se o valor for menor ou igual a zero.
func (c *Conta) Depositar(valor float64) error {
if valor <= 0 {
return ErrValorInvalido
}
c.mu.Lock()
defer c.mu.Unlock()
c.saldo += valor
return nil
}
// Saldo retorna o saldo atual da conta.
// Esta operação é thread-safe.
func (c *Conta) Saldo() float64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.saldo
}
// Titular retorna o nome do titular da conta.
func (c *Conta) Titular() string {
return c.titular
}
// Sacar retira o valor informado do saldo da conta.
// Retorna ErrValorInvalido se o valor for menor ou igual a zero e
// ErrSaldoInsuficiente se o valor for maior que o saldo.
func (c *Conta) Sacar(valor float64) error {
if valor <= 0 {
return ErrValorInvalido
}
c.mu.Lock()
defer c.mu.Unlock()
if valor > c.saldo {
return ErrSaldoInsuficiente
}
c.saldo -= valor
return nil
}
Documentando tipos e constantes
// Conta representa uma conta bancária com operações de depósito, saque e transferência.
// Conta é segura para uso concorrente.
type Conta struct {
titular string
saldo float64
mu sync.RWMutex
}
// StatusConta representa o estado atual de uma conta bancária.
type StatusConta int
const (
// StatusAtiva indica que a conta está ativa e operacional.
StatusAtiva StatusConta = iota
// StatusBloqueada indica que a conta foi bloqueada temporariamente.
// Operações de débito são recusadas enquanto a conta estiver bloqueada.
StatusBloqueada
// StatusEncerrada indica que a conta foi encerrada definitivamente.
// Nenhuma operação é permitida em contas encerradas.
StatusEncerrada
)
// ErrSaldoInsuficiente é retornado quando um saque ou transferência
// excede o saldo disponível na conta.
var ErrSaldoInsuficiente = errors.New("saldo insuficiente")
// ErrTitularVazio é retornado quando o nome do titular é uma string vazia.
var ErrTitularVazio = errors.New("titular não pode ser vazio")
// ErrValorInvalido é retornado quando o valor de uma operação é menor
// ou igual a zero.
var ErrValorInvalido = errors.New("valor deve ser maior que zero")
Formatação especial no godoc
O godoc reconhece alguns padrões de formatação automaticamente:
// Processar executa o pipeline de processamento em três fases:
//
// 1. Validação dos dados de entrada
// 2. Transformação e enriquecimento
// 3. Persistência no banco de dados
//
// Considerações de performance:
//
// A fase de transformação pode ser custosa para entradas grandes.
// Para lotes acima de 10.000 itens, considere usar ProcessarEmLotes.
//
// Exemplos de uso típicos:
//
// // Processamento simples
// err := Processar(ctx, dados)
//
// // Com opções
// err := Processar(ctx, dados, ComParalelismo(4), ComTimeout(30*time.Second))
//
// Código de exemplo completo em https://pkg.go.dev/meu-pacote#example-Processar
func Processar(ctx context.Context, dados []byte, opts ...Opcao) error {
Blocos de código são linhas indentadas em relação ao texto do comentário, e são exibidos como código pré-formatado. Desde o Go 1.19, o gofmt também formata os comentários de documentação: um bloco indentado com dois espaços, como nos exemplos deste artigo, é reescrito com um tab depois do //.
Listas numeradas com o padrão N. são reconhecidas automaticamente desde o Go 1.19.
Links — URLs completas são automaticamente transformadas em hiperlinks.
Exemplos executáveis
Funções de exemplo são verificadas pelo go test e exibidas na documentação:
// arquivo: financeiro/conta_example_test.go
package financeiro_test
import (
"fmt"
"log"
"meu-modulo/financeiro"
)
func ExampleNovaConta() {
conta, err := financeiro.NovaConta("Ricardo Matos", 1000.00)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Titular: %s\n", conta.Titular())
fmt.Printf("Saldo: R$ %.2f\n", conta.Saldo())
// Output:
// Titular: Ricardo Matos
// Saldo: R$ 1000.00
}
func ExampleConta_Depositar() {
conta, _ := financeiro.NovaConta("Ana", 500.00)
if err := conta.Depositar(250.00); err != nil {
log.Fatal(err)
}
fmt.Printf("Saldo após depósito: R$ %.2f\n", conta.Saldo())
// Output:
// Saldo após depósito: R$ 750.00
}
func ExampleConta_Sacar_saldoInsuficiente() {
conta, _ := financeiro.NovaConta("Carlos", 100.00)
err := conta.Sacar(200.00)
fmt.Println(err)
// Output:
// saldo insuficiente
}
A convenção de nomenclatura para exemplos: - ExampleFuncao — exemplo para uma função - ExampleTipo_Metodo — exemplo para um método - ExampleTipo_Metodo_sufixo — múltiplos exemplos para o mesmo método
Rodando o godoc localmente
# Instalar godoc
go install golang.org/x/tools/cmd/godoc@latest
# Iniciar servidor local na porta 6060
godoc -http=:6060
Acesse http://localhost:6060/pkg/meu-modulo/ para ver a documentação do seu módulo renderizada. Nas versões recentes do Go, não é preciso instalar nada: go doc -http, rodado dentro do módulo, abre um servidor com a documentação do pacote atual.
go doc: documentação na linha de comando
# Documentação de um pacote
go doc fmt
# Documentação de uma função específica
go doc fmt.Printf
# Documentação de um tipo
go doc net/http.Request
# Documentação de um campo (métodos usam a mesma forma: Tipo.Metodo)
go doc net/http.Request.Header
# Documentação completa do pacote: tudo o que é exportado
go doc -all ./financeiro/
# Incluindo os identificadores não exportados
go doc -u -all ./financeiro/
# Código-fonte de uma função
go doc -src fmt.Printf
pkg.go.dev: documentação pública
Todo módulo publicado no GitHub (ou qualquer VCS público) aparece automaticamente em pkg.go.dev após ser referenciado por um go get. A documentação é gerada automaticamente a partir dos comentários do código — desde que o repositório tenha uma licença que o site reconheça. Sem ela, a página do módulo aparece, mas a documentação fica oculta, conforme a política de licenças do pkg.go.dev.
Para garantir boa apresentação no pkg.go.dev:
// Package meuservico fornece um cliente HTTP para a API de Pagamentos XYZ.
//
// # Instalação
//
// go get github.com/minha-empresa/meuservico
//
// # Uso rápido
//
// cliente := meuservico.NovoCliente("minha-api-key")
// resposta, err := cliente.CriarPagamento(ctx, meuservico.Pagamento{
// Valor: 100.00,
// Moeda: "BRL",
// Descricao: "Pedido #1001",
// })
//
// # Autenticação
//
// Todas as requisições exigem uma API key válida, obtida no painel em
// https://app.meuservico.com/api-keys
//
// # Tratamento de erros
//
// Erros da API são retornados como *meuservico.ErroAPI, que contém o
// código HTTP e a mensagem de erro detalhada.
package meuservico
Os cabeçalhos # Seção são suportados desde o Go 1.19: o pacote go/doc/comment, que as ferramentas de documentação usam, os converte em <h3>. Para ser reconhecida como cabeçalho, a linha precisa estar sozinha, sem indentação e entre linhas em branco; colada ao parágrafo seguinte, ela vira texto comum, com o # à mostra.
README.md e doc.go
Para pacotes com documentação extensa, é comum separar o comentário do pacote em um arquivo dedicado doc.go:
// doc.go — contém apenas o comentário do pacote e a declaração package
// Package cache implementa uma camada de cache em memória com suporte a
// expiração por TTL, eviction por LRU e métricas de hit/miss.
//
// # Exemplo básico
//
// c := cache.Novo(cache.Config{
// Capacidade: 1000,
// TTL: 5 * time.Minute,
// })
//
// c.Set("chave", "valor")
//
// v, existe := c.Get("chave")
// if existe {
// fmt.Println(v)
// }
//
// # Eviction policy
//
// Quando a capacidade máxima é atingida, o item menos recentemente usado
// (LRU — Least Recently Used) é removido automaticamente.
package cache
Convenções de comentário por tipo
// Comentários de pacote: "Package nome fornece/implementa..."
package autenticacao
// Tipos: "NomeTipo representa/é um/a..."
// Usuario representa um usuário autenticado no sistema.
type Usuario struct { }
// Interfaces: "NomeInterface é implementado por..."
// Autenticador é implementado por qualquer tipo que possa validar credenciais.
type Autenticador interface { }
// Funções construtoras: "NovoTipo cria/retorna..."
// NovoUsuario cria e retorna um novo usuário com o email e senha informados.
func NovoUsuario(email, senha string) (*Usuario, error) { }
// Métodos de leitura: "Nome retorna..."
// Email retorna o endereço de email do usuário.
func (u *Usuario) Email() string { }
// Métodos de escrita: "Nome define/atualiza/registra..."
// DefinirSenha atualiza a senha do usuário com o hash fornecido.
func (u *Usuario) DefinirSenha(hash string) { }
// Métodos booleanos: "Nome relata/verifica se..."
// EstaAtivo relata se o usuário está ativo no sistema.
func (u *Usuario) EstaAtivo() bool { }
// Erros sentinela: "Err... é retornado quando..."
// ErrCredenciaisInvalidas é retornado quando email ou senha não conferem.
var ErrCredenciaisInvalidas = errors.New("credenciais inválidas")
// Constantes: explicar o valor quando não é óbvio
const (
// SessaoMaxima define o tempo máximo de uma sessão inativa antes da expiração.
SessaoMaxima = 30 * time.Minute
// TentativasMaximas define o número de tentativas de login antes do bloqueio.
TentativasMaximas = 5
)
Deprecated: marcando APIs obsoletas
// Conectar estabelece uma conexão com o servidor.
//
// Deprecated: use ConectarComContext para suporte a cancelamento e timeout.
// Esta função será removida na versão 3.0.
func Conectar(host string) (*Conexao, error) {
return ConectarComContext(context.Background(), host)
}
// ConectarComContext estabelece uma conexão respeitando o context fornecido,
// permitindo cancelamento e timeout.
func ConectarComContext(ctx context.Context, host string) (*Conexao, error) {
// implementação atual
}
Ferramentas como staticcheck detectam uso de funções marcadas como Deprecated: e emitem avisos.
Documentação de erros esperados
// Transferir move o valor especificado da conta de origem para a de destino.
//
// Erros possíveis:
// - ErrSaldoInsuficiente: saldo da conta de origem é menor que o valor
// - ErrContaBloqueada: conta de origem ou destino está bloqueada
// - ErrValorInvalido: valor é menor ou igual a zero
// - ErrMesmasConta: origem e destino são a mesma conta
func (s *Servico) Transferir(ctx context.Context, origemID, destinoID string, valor float64) error {
Checklist de documentação
Antes de publicar ou fazer code review, verifique:
□ O pacote tem comentário começando com "Package nome..."?
□ Toda função/método exportado tem comentário começando com seu nome?
□ Todo tipo exportado tem comentário descritivo?
□ Erros sentinela exportados têm comentário explicando quando são retornados?
□ Funções complexas têm exemplos executáveis?
□ Parâmetros com comportamento não óbvio estão documentados?
□ APIs obsoletas estão marcadas com "Deprecated:"?
□ O comentário faz sentido quando lido isoladamente do código?
□ O revive (regra exported, no golangci-lint) não aponta símbolo público sem comentário?
Em Go, a documentação mora no código: um comentário logo acima da declaração, começando pelo nome do identificador, vira a página do pacote no go doc e no pkg.go.dev. Os exemplos testáveis são o recurso que mais rende, porque ilustram o uso e falham no go test quando a saída muda, mas só funcionam se o código que eles chamam existir de fato. A formatação dos comentários é simples, com cabeçalhos #, listas, blocos indentados e links, e o gofmt a mantém uniforme. Para que nada exportado fique sem comentário, a verificação deve ser automática, com a regra exported do revive no golangci-lint, e não um item de checklist lembrado na revisão.
Fontes e leituras recomendadas
-
Go Doc Comments — Guia oficial completo sobre comentários de documentação. https://go.dev/doc/comment
-
pkg.go.dev — Portal de documentação oficial de todos os módulos Go públicos. https://pkg.go.dev
-
Documentação godoc — Referência da ferramenta godoc. https://pkg.go.dev/golang.org/x/tools/cmd/godoc
-
Go Blog: Testable Examples in Go — Como exemplos executáveis funcionam. https://go.dev/blog/examples
-
Go Code Review Comments — Documentation — Convenções de comentário da equipe Go. https://go.dev/wiki/CodeReviewComments#doc-comments
-
staticcheck — SA1019 — Detecção de uso de APIs marcadas como Deprecated. https://staticcheck.dev/docs/checks#SA1019
Exercícios
Exercício 1
Na primeira versão deste artigo, os exemplos chamavam conta.Titular() e conta.Sacar(200.00), mas o pacote não tinha esses métodos. O autor rodou go test ./... para conferir. O que aconteceu com os três exemplos, inclusive os que só usavam métodos existentes?
Ver resposta
✓ Resposta: Exemplos ficam em arquivos _test.go e são compilados junto com o pacote de teste, como qualquer teste. Um único método inexistente impede a compilação do pacote de teste inteiro, e nenhum exemplo roda, nem os corretos. Num teste com o código da primeira versão, o go vet parou em conta.Titular undefined (type *financeiro.Conta has no field or method Titular, but does have unexported field titular); com os dois métodos acrescentados, os três exemplos passaram. É justamente essa a vantagem dos exemplos testáveis sobre um trecho de código num comentário: um exemplo que deixa de compilar ou cuja saída muda faz o go test falhar, enquanto um comentário desatualizado continua lá, errado, sem que ninguém perceba. Vale lembrar que só os exemplos com o comentário // Output: são executados; sem ele, o exemplo é apenas compilado.
Exercício 2
Um desenvolvedor quer revisar as funções auxiliares internas de um pacote e roda go doc -all ./financeiro/, esperando ver tudo. As funções não exportadas não aparecem. Por quê, e qual é o comando?
Ver resposta
✓ Resposta: O -all mostra a documentação completa do pacote, mas só do que é exportado, que é o que um usuário do pacote pode chamar. Para incluir os identificadores não exportados, a flag é -u. Num teste com uma função ajudante, minúscula, o go doc -all não a listou, e o go doc -u -all mostrou func ajudante(). Os dois combinados dão a visão completa, útil para quem mantém o pacote. Outra forma de consultar a documentação é go doc -http, das versões recentes, que abre um servidor com as páginas do pacote atual. Para o código interno de um pacote grande, porém, o caminho mais comum ainda é o editor: o gopls mostra a documentação de qualquer identificador, exportado ou não, ao passar o mouse sobre ele.
Exercício 3
No pkg.go.dev, a seção # Uso rápido de um pacote aparece como texto comum, com o # visível, enquanto # Instalação, logo acima, virou um cabeçalho. No código, as duas linhas parecem iguais. O que diferencia uma da outra?
Ver resposta
✓ Resposta: Um cabeçalho de comentário precisa ser um parágrafo de uma linha só: começar com # e um espaço, sem indentação, e ter linhas em branco antes e depois. Se a linha seguinte tiver texto, as duas formam um parágrafo comum, e o # vira um caractere qualquer. Num teste com o pacote go/doc/comment, que as ferramentas usam, Instalação. entre linhas em branco virou <h3>, mesmo com o ponto final, e # Uso rápido seguido de uma linha de texto saiu dentro de um <p>. A correção é só a linha // vazia depois do cabeçalho. Como a diferença é visual e não gera aviso, conferir o resultado com go doc ou go doc -http antes de publicar evita descobrir o problema na página pública.
Exercício 4
Depois de atualizar o Go e rodar gofmt -w ., um PR que deveria mudar uma linha aparece com dezenas de comentários alterados, em que os blocos de exemplo trocaram dois espaços por um tab depois do //. O código não mudou. O que provocou isso, e como evitar o ruído?
Ver resposta
✓ Resposta: Desde o Go 1.19, o gofmt formata também os comentários de documentação: blocos de código passam a ser indentados com um tab, listas ganham uma indentação padrão, e linhas em branco excedentes são removidas. Num teste com os comentários deste artigo, todo bloco indentado com dois espaços, como // conta, err := ..., foi reescrito como //<tab>conta, err := .... O conteúdo é o mesmo, mas o diff mostra cada linha. O ruído aparece uma vez só, na primeira formatação depois da atualização. O caminho é fazer essa reformatação num commit separado, sem nenhuma outra mudança, para que o PR funcional continue legível; e manter o gofmt no CI, para que o código nunca mais fique fora do padrão a ponto de gerar um diff desses.
Exercício 5
Um checklist de revisão pedia para conferir que "go doc ./... não exibe missing documentation". O revisor roda o comando e recebe um erro. Qual é o erro, e qual ferramenta faz essa verificação?
Ver resposta
✓ Resposta: O go doc mostra a documentação de um pacote ou símbolo por vez e não aceita padrões com .... Num teste, o comando terminou com does not specify a single package. Ele também nunca reclama de símbolos sem comentário: apenas mostra a declaração sem texto. Quem faz essa verificação é a regra exported do revive, disponível no golangci-lint. Num teste com uma função exportada sem comentário, ele apontou exported function SemDoc should have comment or be unexported, e o staticcheck, mesmo com todas as verificações ligadas, não disse nada sobre ela. Uma verificação automática no CI é mais confiável que um item de checklist, porque roda em todo PR e não depende da atenção de quem revisa.