Refatoração de código legado em JavaScript: do A ao Z

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course
```html

Introdução: por que código legado não é uma sentença

Todo desenvolvedor, mais cedo ou mais tarde, enfrenta código legado em JavaScript. Podem ser scripts monolíticos em Node.js, escritos "de qualquer jeito" há 5 anos, ou funções callback confusas que até desenvolvedores seniores têm medo de tocar. No entanto, a refatoração de código legado não é simplesmente "reescrever tudo do zero", mas um trabalho sistemático para melhorar a arquitetura sem alterar o comportamento externo.

Neste artigo, vamos detalhar um plano passo a passo de refatoração em JavaScript (Node.js): desde a análise e testes até a implementação de padrões modernos. Você aprenderá como transformar com segurança um "pântano" em código sustentável, usando exemplos reais.

1. Análise e mapeamento do código legado

Antes de alterar o código, é preciso entender seu estado atual. Comece criando um mapa de dependências e identificando "pontos críticos".

1.1. Ferramentas para análise

Para JavaScript/Node.js, utilize:

  • ESLint com as regras complexity e max-lines — para identificar funções complexas.
  • Plato ou CodeClimate — para visualizar a complexidade ciclomática.
  • Madge — para construir o grafo de dependências dos módulos.

1.2. Identificação de "code smells"

Problemas típicos de JS legado:

  • Funções com mais de 100 linhas.
  • Variáveis globais e mutação de estado.
  • Callback hell (chamadas assíncronas aninhadas).
  • Falta de modularidade (um arquivo com mais de 2000 linhas).
// Exemplo de "code smell": função faz tudo de uma vez
function processUserData(userId) {
    // 100 linhas: validação, consulta ao BD, formatação, envio de email
    // ... difícil de testar e manter
}

2. Preparação da "rede de segurança": testes

Sem testes, refatoração é "roleta-russa". Crie Characterization Tests (testes que fixam o comportamento atual).

2.1. Adicionando testes unitários

Use Jest ou Mocha. Comece pelos caminhos críticos:

// Testando a função existente sem alterá-la
const { processUserData } = require('./legacy');

describe('processUserData (legacy)', () => {
    it('should process user with ID 1', () => {
        const result = processUserData(1);
        expect(result).toHaveProperty('email');
        expect(result.status).toBe('processed');
    });
});

2.2. Testes de integração para Node.js

Se o código trabalha com BD ou API, use supertest para requisições HTTP:

const request = require('supertest');
const app = require('../app');

describe('POST /api/users', () => {
    it('should create user (legacy behavior)', async () => {
        const res = await request(app)
            .post('/api/users')
            .send({ name: 'Test' });
        expect(res.status).toBe(200);
        expect(res.body.id).toBeDefined();
    });
});

3. Refatoração passo a passo: do monolito aos módulos

Vamos dividir o processo em 3 etapas: isolamento, decomposição, otimização.

3.1. Isolamento e wrappers

Não mexa no interior — envolva a função legada em uma nova interface:

// Função legada
function oldFormatDate(date) {
    // ... 50 linhas com mutações
    return formattedDate;
}

// Novo wrapper
function formatDateSafe(date) {
    if (!(date instanceof Date)) {
        throw new Error('Invalid date');
    }
    return oldFormatDate(date);
}

3.2. Decomposição com Extract Function

Separe blocos lógicos em funções individuais:

// Antes: uma função de 80 linhas
function handleOrder(order) {
    // 1. Validação
    if (!order.id || !order.total) { ... }
    // 2. Cálculo de desconto
```                

Blogs

Book Recommendations