Projeto Go de um arquivo só é fácil. E de dez?
\n\nImagine: você está escrevendo em Go um utilitário para análise de logs. No começo, tudo cabe em um único arquivo main.go. Funciona, tudo ótimo. Mas depois de uma semana, o código cresce: surgem funções para ler arquivos, para filtrar, para enviar dados para o banco de dados. Um arquivo se transforma em um monstro de 2000 linhas. Familiar?
Aqui surge a pergunta que atormenta um em cada dois iniciantes (e que rendeu milhares de visualizações no Stack Overflow): como organizar corretamente um projeto Go com vários arquivos? Não apenas espalhar o código pelas pastas, mas fazer com que funcione, compile e não gere erros como undefined ou import cycle not allowed.
Neste artigo, não vamos apenas dar um modelo. Vamos olhar por baixo dos panos do compilador Go e entender como realmente funciona a ligação entre arquivos, pacotes e módulos. Você descobrirá por que go run *.go às vezes salva e às vezes não, e como construir uma arquitetura que escala.
Arquivo, pacote, módulo — a hierarquia básica
\n\nAntes de mergulhar no código, vamos analisar três conceitos-chave. Sem eles, a organização de um projeto Go se torna um jogo de adivinhação.
\n\n- \n
- Arquivo (
.go) — é apenas um arquivo de texto com código. Por si só, não significa nada para o compilador. \n - Pacote (
package) — é um grupo lógico de arquivos que estão no mesmo diretório. Todos os arquivos em uma pasta devem ter a mesma declaraçãopackage. Se na pasta houver arquivos com pacotes diferentes, o compilador emitirá um erro. \n - Módulo (
go.mod) — é a raiz do seu projeto. Ele define o nome do módulo, suas dependências e versões do Go. Um módulo pode conter vários pacotes. \n
Regra de ouro: um diretório = um pacote. A exceção são os testes, mas falaremos sobre eles depois.
\n\nExemplo prático: projeto "Analisador de Logs"
\n\nVamos criar um pequeno projeto que lê um arquivo de log, filtra as linhas por uma palavra-chave e exibe o resultado. Faremos isso primeiro "de forma direta" (um arquivo) e depois dividiremos corretamente em vários arquivos.
\n\nEtapa 1: Tudo em um único arquivo (ruim)
\n\n// main.go\npackage main\n\nimport (\n "bufio"\n "fmt"\n "os"\n "strings"\n)\n\nfunc readLog(path string) ([]string, error) {\n file, err := os.Open(path)\n if err != nil {\n return nil, err\n }\n defer file.Close()\n\n var lines []string\n scanner := bufio.NewScanner(file)\n for scanner.Scan() {\n lines = append(lines, scanner.Text())\n }\n return lines, scanner.Err()\n}\n\nfunc filterLines(lines []string, keyword string) []string {\n var result []string\n for _, line := range lines {\n if strings.Contains(line, keyword) {\n result = append(result, line)\n }\n }\n return result\n}\n\nfunc main() {\n lines, err := readLog("server.log")\n if err != nil {\n fmt.Println("Erro:", err)\n return\n }\n filtered := filterLines(lines, "ERROR")\n for _, l := range filtered {\n fmt.Println(l)\n }\n}\n\n\nFunciona. Mas imagine que readLog se torne mais complexo (precisa de suporte a gzip, codificações) e filterLines fique cheio de expressões regulares. O arquivo se tornará ilegível.
Etapa 2: Dividindo em pacotes (correto)
\n\nVamos criar a estrutura de diretórios:
\n\nlog-analyzer/\n├── go.mod\n├── main.go\n└── pkg/\n └── logreader/\n ├── reader.go\n └── filter.go\n\n\nInicializamos o módulo:
\n\ngo mod init log-analyzer\n\nAgora vamos escrever o código do pacote logreader. Observe: todos os arquivos na pasta pkg/logreader/ têm package logreader.
pkg/logreader/reader.go
\n\npackage logreader