Introdução
Empresas que operam há mais de uma década acumulam sistemas legados – aplicações construídas com tecnologias que já não recebem suporte ou que foram desenvolvidas sem boas práticas de arquitetura. Esses ativos costumam ser críticos para o negócio, mas ao mesmo tempo são fontes de bugs, dificuldade de manutenção e barreiras à inovação. Refatorar esse tipo de software não é apenas uma questão técnica; trata‑se de garantir a continuidade dos serviços enquanto se abre caminho para novas oportunidades.
Estratégias de migração gradual
1. Avaliação e mapeamento do código existente
Antes de tocar em qualquer linha, é essencial mapear as dependências, módulos críticos e pontos de falha. Ferramentas como SonarQube, CodeScene ou scripts personalizados podem gerar relatórios de complexidade ciclomática, acoplamento e cobertura de testes. O objetivo é criar um inventário que sirva de base para decisões de refatoração.
2. Introdução de testes automatizados
A maior barreira ao refatorar código legado é o medo de introduzir regressões. Começar por testes de unidade e testes de integração permite validar o comportamento atual antes de qualquer mudança. Estratégias recomendadas:
- Testes de caixa preta para rotinas críticas (ex.: pagamentos).
- Mocks para dependências externas (APIs, bancos de dados).
- Coverage mínimo de 70% nas áreas que serão modificadas.
3. Aplicação de padrões de design
Padrões como Strategy, Adapter e Facade ajudam a isolar lógica complexa e a criar pontos de extensão claros. Quando aplicados de forma incremental, eles reduzem o acoplamento e facilitam a substituição de componentes antigos por versões mais modernas.
4. Refatoração incremental
Em vez de uma reescrita completa, adote a refatoração em pequenos lotes. Cada lote deve:
- Possuir um conjunto de testes que garantam a estabilidade.
- Ser revisado por pares (code review).
- Ser integrado ao pipeline de CI/CD.
Esse ciclo de test‑refactor‑retest permite entregar valor continuamente e minimizar riscos.
Exemplos Práticos
1. Extraindo lógica complexa para uma função pura (Python)
python
legacy.py – código original (muito acoplado ao DB)
def processar_pedido(pedido_id): conn = criar_conexao() cursor = conn.cursor() cursor.execute("SELECT total FROM pedidos WHERE id=%s", (pedido_id,)) total = cursor.fetchone()[0] # cálculo complexo espalhado aqui if total > 1000: desconto = total * 0.1 else: desconto = 0 total_final = total - desconto atualizar_total(pedido_id, total_final) conn.commit() conn.close() return total_final
Passo 1 – Isolar o cálculo
python
utils.py – nova responsabilidade
def calcular_desconto(total: float) -> float: """Retorna o valor do desconto baseado no total da compra.""" return total * 0.1 if total > 1000 else 0.0
Passo 2 – Injetar dependência e escrever teste
python
test_utils.py
import unittest from utils import calcular_desconto
class TestCalculoDesconto(unittest.TestCase): def test_desconto_acima_de_mil(self): self.assertAlmostEqual(calcular_desconto(1500), 150)
def test_sem_desconto(self):
self.assertEqual(calcular_desconto(500), 0)
if name == 'main': unittest.main()
Passo 3 – Refatorar a função original
python
legacy_refactored.py
from utils import calcular_desconto
def processar_pedido(pedido_id, db): # db é injetado, facilitando mocks total = db.obter_total(pedido_id) desconto = calcular_desconto(total) total_final = total - desconto db.atualizar_total(pedido_id, total_final) return total_final
2. Modularizando código JavaScript com ES6 Modules
javascript // oldApp.js – arquivo monolítico function validarUsuario(usuario) { // regras de validação espalhadas if (!usuario.email.includes('@')) throw new Error('Email inválido'); if (usuario.idade < 18) throw new Error('Menor de idade'); // ... }
function salvarUsuario(usuario) { validarUsuario(usuario); // chamada ao banco legado db.insert('usuarios', usuario); }
Refatoração em módulo
javascript // validators/userValidator.js export function validarEmail(email) { if (!email.includes('@')) throw new Error('Email inválido'); }
export function validarIdade(idade) { if (idade < 18) throw new Error('Menor de idade'); }
export function validarUsuario(usuario) { validarEmail(usuario.email); validarIdade(usuario.idade); }
javascript // services/userService.js import { validarUsuario } from '../validators/userValidator.js'; import db from '../infra/database.js';
export function salvarUsuario(usuario) { validarUsuario(usuario); return db.insert('usuarios', usuario); }
Teste unitário com Jest
javascript // userService.test.js import { salvarUsuario } from './userService.js'; import db from '../infra/database.js';
jest.mock('../infra/database.js');
test('deve salvar usuário válido', () => { const usuario = { email: 'test@example.com', idade: 30 }; salvarUsuario(usuario); expect(db.insert).toHaveBeenCalledWith('usuarios', usuario); });
3. Criando uma view para abstrair tabelas antigas (SQL)
sql -- Antes: consultas espalhadas por toda a aplicação SELECT u.id, u.nome, p.valor FROM usuarios u JOIN pedidos p ON u.id = p.usuario_id WHERE p.status = 'PENDENTE';
Passo 1 – Definir view consolidada
sql CREATE OR REPLACE VIEW vw_pedidos_pendentes AS SELECT u.id AS usuario_id, u.nome AS usuario_nome, p.id AS pedido_id, p.valor AS pedido_valor FROM usuarios u JOIN pedidos p ON u.id = p.usuario_id WHERE p.status = 'PENDENTE';
Passo 2 – Atualizar código da aplicação
sql SELECT * FROM vw_pedidos_pendentes WHERE pedido_valor > 500;
A view centraliza a lógica de join e permite mudar o esquema interno sem impactar os consumidores.
4. Script Bash para aplicar lint e formatador antes do commit
bash #!/usr/bin/env bash
pre-commit.sh – garante qualidade de código
set -e
echo "Executando lint..." npx eslint "src/**/*.js" --max-warnings=0
echo "Aplicando prettier..." npx prettier --write "src/**/*.js"
echo "Tudo pronto para o commit!"
Adicione o script ao hook do Git:
bash
.git/hooks/pre-commit
#!/bin/sh ./scripts/pre-commit.sh
Com esse pequeno passo, toda a equipe mantém um padrão consistente, reduzindo a dívida técnica.
Conclusão
Refatorar sistemas legados exige planejamento, disciplina e uma mentalidade incremental. Ao combinar avaliação detalhada, testes automatizados, padrões de design e entregas pequenas, é possível modernizar a base existente sem interromper o negócio. O próximo passo é escolher um módulo crítico, aplicar o ciclo test‑refactor‑retest e registrar os ganhos de performance ou manutenção. Essa cultura de melhoria contínua transforma dívida técnica em vantagem competitiva.



