Introdução: Por que o código legado em Go exige uma abordagem especial
Go (Golang) é conhecido por sua simplicidade e desempenho, mas mesmo nele, com o tempo, surge código legado: pacotes monolíticos, variáveis globais, ausência de interfaces e testes. Refatorar esse código não é apenas "reescrever de forma bonita", mas uma tarefa estratégica da qual dependem a estabilidade e a velocidade do desenvolvimento.
Neste artigo, veremos técnicas avançadas de refatoração de código legado em Go: desde a extração de módulos até a injeção segura de interfaces. Você aprenderá como transformar "código espaguete" em uma arquitetura sustentável sem parar a produção.
1. Diagnóstico e mapeamento do código legado
Antes da refatoração, é necessário entender a estrutura atual. Em Go, existem ferramentas e abordagens integradas para isso:
- Análise de dependências: use
go mod graphego list -jsonpara visualizar as conexões entre pacotes. - Busca por variáveis globais:
grep -r "var " *.go— muitas vezes elas são a raiz dos problemas. - Identificação de "code smells": funções longas, duplicação, falta de tratamento de erros.
// Exemplo: busca de todas as variáveis globais no projetopackage main
import ( "fmt" "go/parser" "go/token" "os" "path/filepath")
func findGlobals(root string) { filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || filepath.Ext(path) != ".go" { return nil } fset := token.NewFileSet() f, _ := parser.ParseFile(fset, path, nil, parser.AllErrors) for _, decl := range f.Decls { if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.VAR { fmt.Printf("Variável global em %s: %v", path, genDecl.Specs) } } return nil })}Importante: antes da refatoração, crie um snapshot completo dos testes (mesmo que não existam — escreva pelo menos testes smoke para os caminhos críticos).
2. Extração estratégica de módulos: do monolito aos pacotes
O código legado geralmente contém um pacote gigante main ou utils. A refatoração avançada começa com a extração granular:
- Extração por responsabilidade: divida o código em camadas (handler, service, repository).
- Criação de pacotes internos: use
internal/para ocultar a implementação. - Interfaces como contratos: não crie interfaces antecipadamente — extraia-as das implementações concretas.
// Antes da refatoração: tudo em um arquivopackage main
type User struct { ID int Name string}
func SaveUser(db *sql.DB, u User) error { _, err := db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}
// Após a refatoração: divisão em pacotes// internal/repository/user.gopackage repository
type User struct { ID int Name string}
type UserRepository struct { db *sql.DB}
func (r *UserRepository) Save(u User) error { _, err := r.db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}Dica: use o padrão "Strangler Fig" — substitua gradualmente as funções antigas pelas novas, sem remover o código antigo imediatamente.
3. Eliminação de estados globais e injeção de dependências
Variáveis globais em Go são a principal causa de instabilidade e impossibilidade de teste. A técnica avançada é a inversão de controle (IoC) por meio de construtores e interfaces.
// Problema: logger e config globaisvar logger *log.Loggervar config *Config
func Init() { logger = log.New(os.Stdout, "", log.LstdFlags) config = loadConfig()}
// Solução: injeção de de