Introdução: por que refatorar código legado é uma necessidade
Todo desenvolvedor mais cedo ou mais tarde encontra código legado — desatualizado, mal documentado e muitas vezes ilegível. No mundo do JavaScript e Node.js, isso é especialmente relevante: os projetos crescem rapidamente, as tecnologias mudam e as seções de código antigo se transformam em um "pântano" que retarda o desenvolvimento. Refatorar código legado não é apenas um capricho, mas uma necessidade para manter o desempenho, a segurança e a escalabilidade da aplicação.
Neste artigo, vamos detalhar como abordar a refatoração de código legado em JavaScript (Node.js) de forma segura e eficaz. Você aprenderá sobre as principais etapas: análise, divisão em módulos, substituição de construções obsoletas e testes. Todos os exemplos são escritos em JavaScript moderno (ES6+) e são orientados para o ambiente Node.js.
Etapa 1: Análise e compreensão do código legado
Antes de alterar o código, você precisa entendê-lo. O código legado geralmente é escrito em um estilo "espaguete": funções de centenas de linhas, variáveis globais e lógica misturada. Comece com uma auditoria.
1.1. Crie um mapa de dependências
Use ferramentas como madge ou dependency-cruiser para visualizar as conexões entre os módulos. Isso ajudará a identificar dependências circulares e "objetos deus".
// Exemplo: instalando o madgenpm install -g madgemadge --image graph.svg src/1.2. Identifique as "zonas de risco"
Encontre seções de código que mudam com mais frequência ou causam erros. Use o histórico de commits do Git:
git log --oneline --follow src/legacyModule.jsPreste atenção também a:
- Funções com mais de 50 linhas
- Variáveis globais (por exemplo,
varsemlet/const) - Callback hell (chamadas assíncronas aninhadas)
1.3. Escreva testes para o comportamento existente
Sem testes, a refatoração é uma bomba-relógio. Use jest ou mocha para cobrir as funções principais. Os testes devem verificar o comportamento atual, não o "ideal".
// Exemplo de teste para uma função legadaconst sum = require('./legacyModule').sum;test('sum adiciona dois números', () => { expect(sum(1, 2)).toBe(3);});Etapa 2: Melhoria gradual da estrutura
Não tente reescrever tudo de uma vez. Use a estratégia "Strangler Fig" — substitua partes do código gradualmente, mantendo a compatibilidade reversa.
2.1. Divida o monólito em módulos
Extraia blocos logicamente relacionados em arquivos separados. Por exemplo, mova o trabalho com banco de dados para db.js e o tratamento de requisições para handlers.js.
// Antes: um arquivo de 500 linhasfunction handleRequest(req, res) { // 100 linhas de lógica // 50 linhas de trabalho com banco de dados // 30 linhas de formatação de resposta}
// Depois: divisão em módulos// db.jsexports.getUser = (id) => { /* ... */ };
// handlers.jsconst db = require('./db');exports.handleRequest = (req, res) => { const user = db.getUser(req.params.id); // formatação da resposta};2.2. Substitua var por let/const
Este é um passo simples, mas importante. var tem escopo de função, o que geralmente leva a bugs. Use let para variáveis e const para constantes.
// Eravar x = 10;if (true) { var x = 20; // sobrescreve a variável externa}
// Ficoulet x = 10;if (true) { let x = 20; // variável local}Etapa 3: Modernizando código assíncrono
O código legado em Node.js geralmente sofre de "callback hell". Substitua callbacks aninhados por promises ou async/await.
3.1. Convertendo callbacks em promises
Use util.promisify para funções que seguem o estilo Node.js (primeiro argumento é um erro).
const fs = require('fs');const { promisi