Introdução: por que a refatoração de código legado para TypeScript é uma necessidade
Trabalhar com código legado é uma das tarefas mais frequentes e desafiadoras para um desenvolvedor. Frequentemente, esse código é escrito em JavaScript puro, sem tipagem estrita, contém dependências confusas e falta de estrutura modular. A migração para TypeScript não apenas adiciona tipos — é uma melhoria fundamental na qualidade do código, que reduz o número de bugs, simplifica a refatoração e acelera a integração de novos desenvolvedores.
Neste artigo, vamos detalhar o processo passo a passo de refatoração de código legado para TypeScript. Você aprenderá como introduzir tipos com segurança, dividir funções monolíticas e testar as alterações.
Passo 1. Análise e preparação do código legado
Antes de escrever código, é necessário entender a arquitetura atual. Sem isso, a refatoração se tornará um caos.
Crie um mapa de dependências
Comece analisando arquivos e módulos. Identifique:
- Quais funções são "puras" (pure) e quais têm efeitos colaterais.
- Onde estão as variáveis globais e estados.
- Quais partes do código estão duplicadas.
Escreva testes para as partes críticas
Refatoração sem testes é jogar roleta-russa. Escreva pelo menos testes unitários para as funções-chave. Exemplo de teste em TypeScript:
// Código legado original (JavaScript)function calculatePrice(items) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i].price * items[i].quantity; } return total;}
// Teste em TypeScript (Jest)describe('calculatePrice', () => { it('should calculate total price correctly', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculatePrice(items)).toBe(35); });});Se os testes passarem — você pode começar a refatoração.
Passo 2. Introdução gradual de tipagem estrita
Não tente cobrir tudo com tipos de uma vez. Use a estratégia "de baixo para cima": comece pelas funções mais simples e isoladas.
Tipagem de funções simples
Adicione tipos para parâmetros e valores de retorno. Exemplo:
// Antes (JavaScript)function greet(name) { return "Hello, " + name;}
// Depois (TypeScript)function greet(name: string): string { return `Hello, ${name}`;}Trabalhando com objetos e interfaces
Para estruturas de dados complexas, crie interfaces. Isso torna o código autodocumentado:
// Definindo a interface para um item de pedidointerface OrderItem { id: number; name: string; price: number; quantity: number;}
// Função com parâmetro tipadofunction getTotalPrice(items: OrderItem[]): number { return items.reduce((sum, item) => sum + item.price * item.quantity, 0);}Use `any` apenas como solução temporária
O tipo any é inimigo da tipagem estrita. Se você não sabe o tipo exato, use unknown e verifique-o em tempo de execução:
function safeParse(data: unknown): string { if (typeof data === 'string') { return data.trim(); } return '';}Passo 3. Separação de funções e módulos monolíticos
Código legado frequentemente contém "funções divinas" (god functions) que fazem tudo de uma vez. Divida-as em módulos pequenos e reutilizáveis.
Isolando a lógica pura
Exemplo: uma função que valida email e envia uma requisição. Vamos separá-la:
// Antes (monolítico)function processUser(email) { if (!email || !email.includes('@')) { throw new Error('Invalid email'); } // ... envio da requisição console.log('Sending request...');}
// Depois (separação de responsabilidades)function validateEmail(email: string): boolean { return email
```