Introdução: por que o código legado em Go precisa de refatoração?
\nGo (Golang) é conhecido por sua simplicidade, desempenho e ferramentas integradas para concorrência. No entanto, qualquer projeto acumula "dívida técnica" com o tempo: variáveis globais, falta de interfaces, números mágicos, funções ilegíveis e ausência de testes. Esse código é chamado de legado. Refatoração não é reescrever tudo do zero, mas melhorar gradualmente a estrutura sem alterar o comportamento externo. Neste artigo, vamos detalhar um plano passo a passo para refatorar código Go legado, desde a análise até a implementação.
\n\nPasso 1: Diagnóstico do código legado
\nAntes de mudar qualquer coisa, é preciso entender com o que estamos lidando. Os principais "sinais de alerta" em projetos Go:
\n- \n
- Variáveis globais (
var db *sql.DB) — dificultam testes e trabalho paralelo. \n - Funções-monstro (mais de 50-100 linhas) — tornam o código ilegível. \n
- Falta de interfaces — acoplamento rígido a implementações específicas. \n
- Strings/números mágicos — dificultam busca e alteração. \n
- Ausência de testes — refatorar sem testes é andar em gelo fino. \n
Ferramentas de análise: use go vet e staticcheck para encontrar construções suspeitas. gocyclo também é útil para calcular complexidade ciclomática.
Exemplo de código problemático:
\npackage main\n\nimport "fmt"\n\nvar config map[string]string // variável global\n\nfunc main() {\n config = map[string]string{"port": "8080"}\n startServer()\n}\n\nfunc startServer() {\n // string mágica "port"\n fmt.Println("Iniciando na porta:", config["port"])\n // ... 100 linhas de código\n}\n\nPasso 2: Criar uma rede de proteção com testes
\nSem testes, a refatoração vira adivinhação. Sua tarefa é cobrir a lógica principal com testes antes das alterações. Use a abordagem Golden Files (arquivos de referência) para saídas complexas ou testes de integração.
\nExemplo de adição de teste para uma função de código legado:
\n// legacy.go (original)\nfunc CalculateDiscount(price float64, isPremium bool) float64 {\n if isPremium {\n return price * 0.9\n }\n return price * 0.95\n}\n\n// legacy_test.go\npackage main\n\nimport "testing"\n\nfunc TestCalculateDiscount_Premium(t *testing.T) {\n result := CalculateDiscount(100.0, true)\n expected := 90.0\n if result != expected {\n t.Errorf("Esperado %f, obtido %f", expected, result)\n }\n}\n\nfunc TestCalculateDiscount_Regular(t *testing.T) {\n result := CalculateDiscount(100.0, false)\n expected := 95.0\n if result != expected {\n t.Errorf("Esperado %f, obtido %f", expected, result)\n }\n}\nDica: use go test -cover para avaliar a cobertura. Tente cobrir pelo menos 70% da lógica de negócios antes de iniciar a refatoração.
Passo 3: Decomposição gradual e extração de interfaces
\nO principal objetivo da refatoração é tornar o código testável e flexível. Comece extraindo interfaces para dependências externas (banco de dados, clientes HTTP, sistemas de arquivos).
\n3.1 Extração de interface em vez de uso direto do BD
\nSuponha que temos uma função que trabalha diretamente com *sql.DB:
// código legado\nfunc GetUser(db *sql.DB, id int) (*User, error) {\n row := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)\n // ...\n}\nRefatoração: criar interface UserRepository:
type UserRepository interface {\n GetByID(id int) (*User, error)\n}\n\ntype userRepo struct {\n db *sql.DB\n}\n\nfunc (r *userRepo) GetByID(id int) (*User, error) {\n row := r.db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)\n