Como apresentar refatorações de código legado em entrevistas técnicas
Por John Pires · 10 de março de 2026

Cenario pratico do dia a dia
O que é um código legado no contexto da entrevista
Você será questionado sobre impactos reais: manutenção, bugs frequentes, acoplamento e riscos. Em entrevistas técnicas, 'código legado' não é apenas código antigo — é qualquer base onde mudanças geram efeitos colaterais inesperados e onde falta teste ou documentação. Mostrar que você entende esse contexto é mais valioso do que decorar padrões.
Como os entrevistadores querem ver sua abordagem
Recrutadores e engenheiros buscam clareza: identificação do problema, análise de trade-offs, testes, pequenas mudanças incrementais e comunicação. Eles querem ver que você prioriza segurança (não quebrar produção), que sabe dividir um refactor em passos reversíveis e que tem critérios de sucesso mensuráveis.
Na prática, você deve demonstrar: leitura rápida do código, identificação de responsabilidades mal distribuídas, hipóteses de regressão e mitigação com testes e deploy incremental. Seja específico sobre riscos e decisões.
Exemplos genéricos não bastam aqui. Use um exemplo concreto: uma função que valida pedido, calcula total e persiste tudo junto. Explique por que isto é um problema — dificulta testes, impossibilita reuso e torna deploys arriscados.
Antes de sugerir mudanças, explique o comportamento atual, as falhas observadas e as métricas que importam: tempo médio de deploy, taxa de rollback, cobertura de teste.
Arquitetura da solucao
Princípios para decomposição segura
Adote princípios simples e comunicáveis: SRP (single responsibility), testes por função, interfaces claras e isolamento de efeitos colaterais. Em entrevistas, prefira padrões que você pode explicar em 30-60 segundos e demonstrar com código curto.
Estratégia incremental e contrato público
Proponha uma arquitetura que permita dois modos de operação: manter compatibilidade com a API atual e introduzir funções internas fáceis de testar. Use adapters e wrappers pequenos para proteger consumidores e desencadear rollback rápido se necessário.
Um fluxo razoável para apresentar: identificar função-alvo, extrair validação, extrair cálculo, criar testes unitários, trocar implementação interna e, por fim, remover código antigo com um commit separado. Isso mostra disciplina e cuidado com riscos.
- Extração pequena e comprovada: um commit por responsabilidade
- Testes antes de alterar lógica (onde possível)
- Documentação curta no PR com antes/depois e mitigação de riscos
Codigo completo com explicacoes
Contexto do exemplo
Vou usar um exemplo de backend em Node.js: uma função 'processOrder' que recebe um pedido, valida, calcula total com descontos, atualiza estoque e grava no banco. Esse é um caso comum de função com muitas responsabilidades.
Exemplo 1 — Código legado (funcional)
O código abaixo representa a função original encontrada no legado. Ele é intencionalmente monolítico e imprimível com node. Antes do bloco, explique: este arquivo processa pedidos e mistura validação, cálculo e persistência.
// legacy-process-order.js
// O que faz: processa um pedido inteiro — valida, calcula e persiste
function processOrder(order) {
// validações simples inline
if (!order || !order.items || order.items.length === 0) {
throw new Error('pedido invalido');
}
// calcula total com desconto aleatório (lógica misturada)
let total = 0;
for (const it of order.items) {
if (!it.price || !it.qty) {
throw new Error('item invalido');
}
total += it.price * it.qty;
}
// desconto por quantidade (lógica de negocio dentro)
if (order.items.length > 3) {
total = total * 0.9; // 10% desconto
}
// atualiza estoque (simulado) e persiste
// side effects diretos e sem isolamento
for (const it of order.items) {
// imaginando chamada ao banco/estoque
console.log('decrement stock for', it.sku);
}
// grava order (simulado)
console.log('persist order with total', total);
return { success: true, total };
}
// Exemplo de uso
const example = { items: [ { sku: 'A1', price: 10, qty: 2 }, { sku: 'B2', price: 5, qty: 1 } ] };
console.log(processOrder(example));
Explicacao depois do bloco: esse código funciona mas é difícil de testar partes isoladas — validação, cálculo e persistência são acopladas. Em entrevista, destaque isso: pontos de dor, como cobertura e risco de regressão.
Exemplo 2 — Versão refatorada com testes simples
Agora, extraí validação e cálculo para funções puras e isolei efeitos colaterais. Antes do bloco: este arquivo mostra a decomposição com funções exportáveis e um mock de persistência.
// refactor-process-order.js
// O que faz: mesma funcionalidade, com responsabilidades separadas
function validateOrder(order) {
if (!order || !order.items || order.items.length === 0) {
throw new Error('pedido invalido');
}
for (const it of order.items) {
if (!it.price || !it.qty) {
throw new Error('item invalido');
}
}
return true;
}
function calculateTotal(order) {
let total = 0;
for (const it of order.items) {
total += it.price * it.qty;
}
if (order.items.length > 3) {
total = total * 0.9;
}
return total;
}
async function persistOrder(order, total, dbClient) {
// dbClient.save é um mockable side-effect
await dbClient.save({ order, total });
return true;
}
async function processOrderRefactored(order, dbClient) {
validateOrder(order);
const total = calculateTotal(order);
await persistOrder(order, total, dbClient);
return { success: true, total };
}
// Mock de dbClient para demonstracao
const mockDb = { save: async (payload) => console.log('saved', payload.total) };
// Uso
const example = { items: [ { sku: 'A1', price: 10, qty: 2 }, { sku: 'B2', price: 5, qty: 1 } ] };
processOrderRefactored(example, mockDb).then(r => console.log(r));
Explicacao depois do bloco: agora você pode testar calculateTotal sem tocar banco, validar inputs isoladamente e mockar persistOrder em testes de integração. Em entrevista, cite que você faria commits separados: extrair funções puras, adicionar testes, then adaptar callers.
Exemplo 3 — Teste rápido e benchmark
Antes do bloco: mostre um benchmark simples comparando a versão legacy e a refatorada em execução repetida. Isso é útil para discutir trade-offs de performance e justificar mudanças.
// benchmark.js
// O que faz: compara tempo de execução entre legacy e refactor em loops
const { performance } = require('perf_hooks');
// Implementacoes simples (copiadas/inlined para comparacao)
function legacyCalc(order) {
let total = 0;
for (const it of order.items) {
total += it.price * it.qty;
}
if (order.items.length > 3) total = total * 0.9;
return total;
}
function refactorCalc(order) {
// mesma logica em funcao separada — overhead de chamada de funcao
return (function calc(o) {
let t = 0;
for (const i of o.items) t += i.price * i.qty;
if (o.items.length > 3) t = t * 0.9;
return t;
})(order);
}
const order = { items: new Array(10).fill(0).map((_,i)=>({ sku: 's'+i, price: 10+i, qty: 1 })) };
function bench(fn, iter=100000) {
const start = performance.now();
let s = 0;
for (let i=0;i<iter;i++) s += fn(order);
const end = performance.now();
return { ms: end-start, sum: s };
}
console.log('legacy', bench(legacyCalc));
console.log('refactor', bench(refactorCalc));
Explicacao depois do bloco: o benchmark simples mostrará se a decomposição impõe overhead relevante. Em sistemas I/O-bound, overhead de chamadas internas é irrelevante. Em entrevistas, use esse argumento para justificar ou não uma micro-otimização.
Tratamento de erros e edge cases
Erros comuns ao apresentar refactors
Um erro frequente é falar alto demais sobre padrões sem mostrar provas. Outro é alterar too much at once: grandes commits que misturam refatoração com features novas. Isso leva a rollbacks e menos confiança do time. Em entrevista, proponha commits pequenos e reversíveis.
Armadilhas técnicas e como mitigá-las
Armadilhas comuns: assumir que testes existentes cobrem casos, remover comportamento legado com impacto sutíl (por exemplo, rounding), e ignorar código que expõe contratos implícitos aos clientes. Mitigue criando testes que documentem o comportamento atual antes de mudar.
Inclua no seu roteiro de apresentação: como você testa alterações, quais mocks usa, que logs ou métricas adicionaria temporariamente para validar em produção e como faria rollback.
- Teste antes: unidade para lógica pura, integração para persistência
- Commits pequenos: um compromisso por responsabilidade extraída
- Documente o contrato: quais entradas e saídas manteve compatíveis
Ao descrever erros, sempre entregue como ação: 'para evitar X eu adiciono teste Y e um commit separado que isola side-effects'.
Performance e otimizacao
Medindo impacto real
Performance é sobre métricas: latency, throughput e custo. Em entrevistas, não diga apenas 'é mais rápido' — mostre números simples. Use microbenchmarks para validar overheads e monitore em staging para ver impacto sob carga real.
Quando não otimizar
Não otimize prematuramente. Se refactor melhora legibilidade e testabilidade, e o sistema é I/O-bound, aceitar um pequeno overhead de CPU é razoável. Explique trade-offs: manutenção vs latência. Tenha exemplos concretos: 'desacoplei cálculo da persistência e isso adicionou 2 chamadas de função — overhead insignificante comparado ao chamado ao banco'.
Demonstre que você sabe medir e justificar: use ferramentas simples (console.time, perf_hooks em Node, benchmarks locais) e dados de produção quando possível. Um bom argumento é 'benchmark local mostrou 3% mais latência em CPU-bound, mas cobertura aumentou 40% e tempo de entrega caiu'.
Conclusao pratica
Roteiro prático para apresentar em entrevista
Estruture sua resposta em 5 passos curtos: (1) contexto e evidências do problema; (2) objetivo do refactor; (3) mudanças concretas (commits pequenos); (4) testes e mitigação de riscos; (5) métricas e trade-offs. Repita esse roteiro em voz curta durante a entrevista.
Checklist rápido para levar na cabeça
Tenha sempre preparado: um snippet do código atual e refatorado, um teste que demonstra comportamento, um benchmark simples e um exemplo de mensagem de PR. Isso mostra que você não apenas entende patterns, mas sabe aplicá-los com disciplina.
Em entrevistas técnicas, seu diferencial é a clareza: dividir problemas, priorizar segurança e provar resultados. Mostre commits pequenos, testes que documentam o comportamento antigo e novos, e números quando possível. Essas práticas reduzem risco e demonstram maturidade técnica.
Feche sua apresentação com uma nota prática: o que outra pessoa no time precisaria saber para revisar o PR e qual rollback plan você deixaria pronto. Isso revela que você pensa além do código.
Use este artigo como script: pratique explicando cada bloco de código do exemplo em 2 minutos. Se dominar esse fluxo, você terá uma resposta técnica concreta e memorável.
Assuntos: entrevistas, refatoracao, codigo-legado, Carreira, Git
Leia também
Entrevistas de pair programming: praticar, ferramentas e exercícios reais
Guia prático para dominar entrevistas de pair programming: quando usar, comparação com alternativas, implementação progressiva, integração com ferramentas…
LinkedIn para dev júnior: otimizar perfil, projetos e templates de mensagem
Guia prático para devs júnior aprimorarem LinkedIn em 2026: otimize About, transforme projetos em provas e use templates de mensagem que funcionam — com…
Negociação salarial para dev júnior: script, cálculo do piso e roleplay prático
Guia prático para dev júnior: calcule seu piso salarial, construa contra-propostas objetivas e treine com scripts e roleplays. Inclui scripts Python/JS, erros…
Usar IA para melhorar seu portfólio técnico sem inventar resultados
Aprenda a usar IA para melhorar descrições e organização do seu portfólio técnico sem inventar resultados: padrões, scripts, validações automáticas e…
Teste take‑home: checklist prático — README, testes, Docker, CI e entrega
Checklist prático para testes take‑home: README claro, testes, Docker, CI e como estruturar a entrega. Guia passo a passo com templates, scripts e armadilhas…
Respostas STAR para entrevistas técnicas: 10 exemplos práticos para júnior
Aprenda a estruturar respostas STAR para entrevistas técnicas com 10 exemplos práticos, scripts para gerar respostas em Python e Node.js, e um mini-projeto…