Você já passou horas refatorando um código que funcionava perfeitamente, só pra deixar ele “mais bonito”? Ou pior: já evitou mexer num código legado horrível com medo de quebrar tudo? A real é que nem toda refatoração vale a pena e nem todo código feio precisa ser reescrito.

No dia a dia, tempo é dinheiro. E ficar refatorando código por ego ou por “boas práticas” sem critério é jogar grana fora. Ao mesmo tempo, ignorar código problemático pode custar muito mais caro no futuro.

Vamos direto ao ponto: como saber quando refatorar e quando deixar quieto?

Refatoração tem que ter retorno

Se você vai gastar 4 horas refatorando um código que ninguém mais vai mexer, você tá perdendo tempo. Simples assim.

Esse código é modificado frequentemente? Tá atrapalhando novas features? Tem bugs recorrentes? Novos devs ficam presos tentando entender? Se a resposta for “não”, deixa como está. Código funcionando não se mexe sem motivo.

Quando você PRECISA refatorar

Tem código feio que funciona. E tem código que é uma bomba-relógio. Aprenda a diferença:

1. Você perde muito tempo tentando entender o que o código faz

Se toda vez que você ou alguém do time precisa mexer ali, demora 15 minutos só pra entender o fluxo, tá na hora. Lembra do que falei sobre nomes honestos e Early Return no artigo de Clean Code? Aqui é a mesma coisa: se você precisa de debugger pra entender uma função simples, algo tá muito errado. Refatora agora antes que vire rotina.

2. O mesmo bug aparece várias vezes na mesma área

Código confuso gera bugs. Se você já corrigiu 3 bugs parecidos no mesmo trecho em 6 meses, o problema é estrutural. Refatora antes que apareça o quarto.

3. Você tem medo de mexer no código

Sério. Se você fica com receio de alterar uma função porque “pode quebrar alguma coisa em outro lugar”, isso é um sintoma claro de acoplamento forte demais. Refatore pra isolar responsabilidades.

4. O código duplicado tá te atrapalhando

Não é sobre DRY religioso. É sobre manutenção. Se você corrige um bug e precisa copiar o fix pra outros 5 lugares, você precisa extrair isso. Simples.

TypeScript
// Ruim: lógica duplicada em 3 endpoints diferentes
app.post('/user', (req, res) => {
  if (!req.body.email || !req.body.email.includes('@')) {
    return res.status(400).json({ error: 'Email inválido' });
  }
  // resto do código...
});

app.put('/user/:id', (req, res) => {
  if (!req.body.email || !req.body.email.includes('@')) {
    return res.status(400).json({ error: 'Email inválido' });
  }
  // resto do código...
});

// Bom: validação extraída
function validateEmail(email: string): boolean {
  return email && email.includes('@');
}

app.post('/user', (req, res) => {
  if (!validateEmail(req.body.email)) {
    return res.status(400).json({ error: 'Email inválido' });
  }
  // resto do código...
});

Código legado: mexe ou não mexe?

A regra de ouro: se tá funcionando em produção há anos e ninguém mexe, NÃO REFATORE.

Mas se você precisa adicionar features nesse código legado, aí muda o jogo:

A estratégia do Strangler Pattern

Não reescreve tudo de uma vez. Vai isolando partes aos poucos:

  1. Identifica a parte que você precisa mexer
  2. Extrai pra uma função/classe nova e limpa
  3. Testa bem
  4. Aos poucos, vai migrando o resto
TypeScript
// Código legado gigante e confuso
function processOrder(data: IData) {
  // 300 linhas de código misturando validação, 
  // cálculo, salvamento no DB, envio de e-mail...
}

// Refatoração incremental - começa extraindo uma parte
function validateOrderData(data: IData): boolean {
  // Lógica de validação extraída e testável
  return data.items?.length > 0 && data.total > 0;
}

function processOrder(data: IData) {
  if (!validateOrderData(data)) {
    throw new Error('Dados inválidos');
  }
  // Resto do código legado continua igual por enquanto
  // ...
}

Aos poucos você vai quebrando aquele monstrinho em pedaços gerenciáveis.

Refatoração que é perda de tempo

Renomear variáveis só porque “não segue o padrão”

Se o código é claro, funciona e ninguém reclama, deixa. Não gasta 2 horas renomeando userInfo pra userData porque você leu que “info” é genérico demais.

Aplicar design patterns porque “é a forma correta”

Factory, Strategy, Observer… tudo lindo na teoria. Mas se você tá criando 5 classes pra fazer algo que uma função resolve, você tá complicando desnecessariamente.

Reescrever em outra linguagem/framework “porque é melhor”

A não ser que tenha um motivo técnico real (performance, manutenção impossível), não cai nessa. “Vou reescrever tudo em Rust porque é mais rápido” geralmente termina em projeto abandonado.

Refatorar pra “aprender” em produção

Quer aprender um pattern novo? Faz um side project. Código de produção não é laboratório.

Como convencer o time a refatorar

Fala em tempo e dinheiro, não em “código limpo”:

“Gastamos 8 horas esse mês debugando essa área. Se refatorarmos agora (4 horas), economizamos esse tempo todo mês.”

Ou:

“Cada nova feature nesse módulo demora 2x mais que deveria porque o código é confuso. Refatorando, velocidade de entrega aumenta.”

Gestor entende ROI. Mostra o número.

Pragmatismo > purismo

Código perfeito não existe. Código que funciona, traz valor e é mantível, existe.

Refatore quando faz sentido, não quando é bonito. No final, o melhor código é aquele que entrega valor pro usuário final. Se o usuário tá feliz e o código não tá atrapalhando o time, tá valendo. Nem todo código precisa ser uma obra de arte.