Modernizar código legado com Claude Code com segurança
Workflow prático para modernizar código legado com Claude Code: testes, TypeScript, riscos, exemplos executáveis e verificação.
Modernização falha quando começa grande demais
Código legado não é apenas código antigo. É código cujo comportamento é difícil de provar, com poucos testes, regras de negócio implícitas e risco real para pedidos, cobrança, suporte ou publicação. Claude Code ajuda muito nesse cenário, mas não deve ser usado como botão de reescrita em massa.
O caminho seguro é investigar primeiro, travar o comportamento atual com testes depois, e só então refatorar em passos pequenos. Testes de caracterização registram como o sistema funciona hoje, mesmo que esse comportamento não seja o ideal. Harness, aqui, é o andaime que permite o trabalho do agente: testes, permissões, comandos, regras do projeto e checklist de revisão.
A documentação oficial de Claude Code common workflows cobre exploração de codebase, refatoração, testes, PRs e worktrees. Essa é a mentalidade correta para código legado: Claude Code aumenta a velocidade, mas o time continua responsável por risco, prioridade e aceite.
Três casos de uso práticos
Não escolha a prioridade só porque um arquivo parece feio. Comece pelas áreas em que reduzir incerteza reduz risco de negócio.
| Caso de uso | Objetivo | O que Claude Code pode fazer | O que humanos devem revisar |
|---|---|---|---|
| Pedidos, cobrança ou pagamento | Evitar erro de dinheiro e status do cliente | Mapear comportamento, adicionar testes, achar limites | Impostos, descontos, arredondamento, regras legais |
| Migração de JavaScript para TypeScript | Tornar mudanças futuras mais seguras | Adicionar tipos, reduzir any, corrigir erros em lotes | Compatibilidade da API pública |
| Callbacks ou funções gigantes | Melhorar manutenção sem alterar comportamento | Separar responsabilidades, sugerir nomes, explicar diffs | Erros, retries, efeitos colaterais e logs |
No ClaudeCodeLab, uso o mesmo fluxo para scripts antigos de publicação, integrações de checkout e transformações de conteúdo. O ponto principal não é Claude Code escrever rápido. O ponto principal é cada mudança ser pequena o suficiente para revisão e ligada a uma prova.
flowchart LR
A[Explorar] --> B[Travar comportamento com testes]
B --> C[Refatorar em passos pequenos]
C --> D[Adicionar tipos e organizar dependências]
D --> E[Revisão humana dos riscos]
E --> B
Comece com uma auditoria somente leitura
O primeiro prompt deve proibir edições. Nessa fase, você precisa de um mapa do sistema, não de um patch baseado em contexto incompleto.
Leia @src/legacy e @test.
Ainda não modifique arquivos.
Retorne esta auditoria:
1. Arquivos principais e responsabilidades
2. I/O externo, banco de dados, APIs, escrita em arquivos e efeitos colaterais
3. Comportamentos que precisam continuar compatíveis
4. Testes ausentes e ramos de alto risco
5. Ordem mais segura para mudanças pequenas
Se uma regra não estiver clara, escreva "precisa de confirmação humana" em vez de adivinhar.
A página oficial How Claude Code works explica que Claude Code pode ler arquivos, executar comandos e editar código. Esse poder é útil, mas em sistemas antigos a primeira fase deve ficar restrita a entendimento. Uma melhoria aparentemente idiomática pode alterar uma resposta usada por outro serviço.
Exemplo mínimo executável
O exemplo abaixo é pequeno, mas segue a mesma sequência que uso em trabalho real com legado.
mkdir legacy-modernization-demo
cd legacy-modernization-demo
npm init -y
npm install -D vitest typescript @types/node
npm pkg set type="module"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.typecheck="tsc --noEmit"
mkdir -p src/legacy test
O processador antigo mistura validação, cálculo e montagem da resposta em um arquivo. Não é um exemplo absurdamente ruim; é o tipo de código que funciona e, por isso, fica anos sem ser tocado.
// src/legacy/orderProcessor.js
export function processOrder(order) {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return { status: "error", message: "items is required" };
}
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.qty;
}, 0);
const discount = order.customer?.type === "vip" ? subtotal * 0.1 : 0;
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Agora trave o comportamento atual com Vitest. Esses testes não redesenham o domínio; eles protegem contratos que não podem mudar sem decisão explícita.
// test/orderProcessor.test.ts
import { describe, expect, it } from "vitest";
import { processOrder } from "../src/legacy/orderProcessor.js";
describe("processOrder legacy behavior", () => {
it("calculates total for a regular customer", () => {
const result = processOrder({
items: [
{ id: "A1", qty: 2, price: 1000 },
{ id: "B2", qty: 1, price: 500 }
],
customer: { id: "C1", type: "regular" }
});
expect(result).toMatchObject({
status: "confirmed",
total: 2500,
discount: 0
});
});
it("applies a 10 percent VIP discount", () => {
const result = processOrder({
items: [{ id: "A1", qty: 1, price: 10000 }],
customer: { id: "C2", type: "vip" }
});
expect(result.status).toBe("confirmed");
expect(result.total).toBe(9000);
expect(result.discount).toBe(1000);
});
it("returns an error when items are empty", () => {
const result = processOrder({
items: [],
customer: { id: "C3", type: "regular" }
});
expect(result.status).toBe("error");
expect(result.message).toContain("items");
});
});
Execute npm test. Só depois de passar, peça para Claude Code editar.
Leia @src/legacy/orderProcessor.js e @test/orderProcessor.test.ts.
Migre este código para TypeScript mantendo os testes verdes.
Regras:
- Mantenha o nome público processOrder
- Preserve status, total, discount e message
- Adicione tipos primeiro e depois separe responsabilidades
- Execute npm test e npm run typecheck
- Explique que compatibilidade cada diff preservou
Uma estrutura TypeScript mais revisável
Depois da modernização, tipos, validação, cálculo e orquestração ficam separados. A meta não é abstração bonita, e sim lógica de negócio mais fácil de revisar.
// src/orderTypes.ts
export type CustomerType = "regular" | "vip";
export type OrderItem = {
id: string;
qty: number;
price: number;
};
export type OrderInput = {
items: OrderItem[];
customer: {
id: string;
type: CustomerType;
};
};
export type OrderResult =
| {
status: "confirmed";
total: number;
items: OrderItem[];
discount: number;
}
| {
status: "error";
message: string;
};
// src/validators.ts
import type { OrderInput } from "./orderTypes";
export function validateOrder(order: OrderInput | null | undefined): string | null {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return "items is required";
}
return null;
}
// src/calculators.ts
import type { CustomerType, OrderItem } from "./orderTypes";
export function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
export function calculateDiscount(subtotal: number, customerType: CustomerType): number {
return customerType === "vip" ? subtotal * 0.1 : 0;
}
// src/orderProcessor.ts
import { calculateDiscount, calculateSubtotal } from "./calculators";
import type { OrderInput, OrderResult } from "./orderTypes";
import { validateOrder } from "./validators";
export function processOrder(order: OrderInput): OrderResult {
const validationMessage = validateOrder(order);
if (validationMessage) {
return { status: "error", message: validationMessage };
}
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer.type);
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Altere o import do teste para ../src/orderProcessor, depois rode npm test e npm run typecheck. Um diff desse tamanho ainda é fácil de revisar. Se o mesmo PR mover pastas, atualizar dependências, trocar formatação e renomear conceitos de domínio, a revisão perde força.
Separe atualizações de dependências
Outro erro comum é misturar refatoração com upgrades major. Quando os testes quebram, fica difícil saber se a causa é TypeScript, uma mudança de API, o bundler ou sua própria lógica.
Peça primeiro um inventário.
Leia package.json e o lockfile.
Não atualize nada ainda.
Retorne uma tabela com:
- package
- versão atual
- versão alvo recomendada
- se é major upgrade
- URL do guia oficial de migração
- arquivos provavelmente afetados
- testes que devemos adicionar antes da atualização
Para operações destrutivas ou amplas, mantenha permissões conservadoras. A documentação de Claude Code permissions vale ser lida antes de autorizar migrações, exclusões ou deploys. Velocidade do agente não ajuda se ela remove a etapa de aprovação que protegia o sistema.
Armadilhas concretas
A primeira armadilha é refatorar sem testes. Código mais limpo ainda é regressão se arredondamento, desconto ou mensagem de erro mudarem.
A segunda é aceitar sugestão de Claude Code como regra de negócio. Um retorno mais idiomático não significa que clientes antigos conseguem consumi-lo.
A terceira é criar um PR gigante. Migração de tipos, separação de lógica, atualização de dependências, movimentação de arquivos e formatação devem ser separados quando possível.
A quarta é melhorar tratamento de erros rápido demais. Em sistemas antigos, um null, uma string específica ou um status HTTP estranho pode ser contrato.
A quinta é deixar documentação para o fim. Peça para Claude Code escrever notas de compatibilidade, passos de verificação manual e plano de rollback na descrição do PR.
Revisão, links e CTA
Combine este fluxo com o guia de automação de refatoração, TDD com Claude Code e geração automática de documentação. Para times, coloque áreas proibidas, comandos de teste, termos de domínio e regras de revisão em boas práticas de CLAUDE.md.
ClaudeCodeLab ajuda equipes a introduzir Claude Code em produtos existentes com treinamento, templates CLAUDE.md e consultoria de modernização. O objetivo não é reescrita por IA, mas um fluxo repetível em que testes, permissões e revisão humana trabalham juntos.
Resultado verificado
Testei o fluxo criando legacy-modernization-demo: processador JavaScript legado, três comportamentos travados com Vitest, migração para TypeScript e execução de npm test com npm run typecheck. O maior ganho veio de separar auditoria somente leitura e prompt de edição. Os diffs ficaram menores, e foi mais fácil verificar total, desconto VIP e erro de pedido vazio.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.