Introdução: Por que o código legado em Go precisa de refatoração?
Go (Golang) é conhecido por sua simplicidade e desempenho. No entanto, mesmo em projetos Go, com o tempo, acumula-se código legado — construções obsoletas, funções ilegíveis, números "mágicos" e falta de testes. Trabalhar com esse código se torna um pesadelo: cada alteração é arriscada e adicionar novas funcionalidades leva semanas.
Refatoração não é reescrever o projeto do zero. É uma reorganização segura da estrutura interna do código sem alterar seu comportamento externo. Neste artigo, vamos detalhar um plano passo a passo para refatorar código legado em Go, desde a análise até a implementação. Você aprenderá como transformar um "código espaguete" confuso em um projeto limpo, sustentável e testável.
Etapa 1: Auditoria e mapeamento do código legado
Antes de alterar o código, você precisa entender com o que está lidando. Pule esta etapa — e corre o risco de quebrar uma lógica que ninguém lembra.
1.1. Identificação de "bandeiras vermelhas"
Comece procurando antipadrões típicos em Go:
- Funções God — funções com mais de 100 linhas.
- Variáveis globais — alto acoplamento e dificuldade de teste.
- Números/strings mágicos — constantes espalhadas pelo código.
- Falta de tratamento de erros — ignorar o retorno de
error. - Pânico em vez de erros — uso de
panic/recoverpara lógica de negócios. - Mistura de camadas — consultas SQL dentro de manipuladores HTTP.
1.2. Criação de um mapa de dependências
Use ferramentas como go callgraph ou guru para visualizar chamadas de funções. Isso ajudará a identificar:
- Dependências cíclicas entre pacotes.
- Código "morto" que não é chamado em lugar nenhum.
- Caminhos críticos que não devem ser tocados sem cobertura de testes prévia.
Etapa 2: Criação de uma "rede de segurança" de testes
Refatoração sem testes é um salto sem rede de segurança. Sua tarefa é escrever testes de caracterização que fixem o comportamento atual do sistema, mesmo que pareça incorreto.
2.1. Testes no nível de funções
Comece pelas partes mais críticas e isoladas do código. Exemplo para uma função que será refatorada:
// Código legadofunc CalculatePrice(base float64, discountCode string) float64 { if discountCode == "SALE10" { return base * 0.9 } if discountCode == "VIP20" { return base * 0.8 } return base}
// Teste de caracterizaçãofunc TestCalculatePrice(t *testing.T) { tests := []struct { name string base float64 code string want float64 }{ {"sem desconto", 100.0, "", 100.0}, {"desconto 10%", 100.0, "SALE10", 90.0}, {"desconto 20%", 100.0, "VIP20", 80.0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := CalculatePrice(tt.base, tt.code) if got != tt.want { t.Errorf("CalculatePrice(%v, %v) = %v; want %v", tt.base, tt.code, got, tt.want) } }) }}2.2. Testes de integração com Golden Files
Para estruturas complexas (respostas JSON, geração de relatórios), use a técnica de "arquivos dourados". Grave a saída atual em um arquivo e, durante a refatoração, compare com ele.
func TestHandler(t *testing.T) { // Executa a requisição req := httptest.NewRequest("GET", "/api/data", nil) rec := httptest.NewRecorder() myHandler(rec, req)
// Compara com o padrão expected, _ := os.ReadFile("testdata/golden.json") if !bytes.Equal(rec.Body.Bytes(), expected) { t.Errorf("Output mismatch") }\