Técnicas de Refatoração de Código para Times de Engenharia
Resumo
TL;DR: As técnicas de refatoração de código mais eficazes combinam Extract Method (para legibilidade imediata) com análise de hotspot (complexidade ciclomática + frequência de alteração). Refatorar just-in-time, antes de cada feature, é mais sustentável do que projetos dedicados. Ferramentas de IA ajudam no mecânico, mas não decidem onde refatorar.
Tem um módulo que ninguém quer tocar. Os bugs se concentram ali. Cada PR que encosta nele leva o dobro do tempo para revisar. Três engenheiros do time já o chamaram de "ala assombrada". Você sabe que precisa refatorar, mas também sabe que a última pessoa que tentou ficou dois sprints dentro e saiu com uma feature flag quebrada e uma expressão levemente derrotada.
As técnicas de refatoração de código existem num espectro que vai de "renomear a variável" até "reestruturar toda a fronteira de serviço". Este guia cobre as que realmente movem métricas: tempo até o primeiro commit de engenheiros novos, taxa de bugs por módulo e duração das code reviews.
Extract Method: A Refatoração que Você Pode Fazer Hoje
Se você só puder aplicar uma técnica em uma base de código, Extract Method é essa. Você identifica um bloco de código dentro de uma função longa que faz uma única coisa coerente, extrai para uma função nomeada e obtém dois benefícios imediatos: a função pai se torna legível, e a função extraída se torna testável isoladamente.
A regra de ouro que uso: se você precisa escrever um comentário para explicar o que um bloco de código faz, esse bloco deveria ser uma função. O nome da função vira o comentário e, ao contrário dos comentários, nomes de função quebram o build quando ficam desatualizados. É um mecanismo de autodocumentação que funciona de verdade.
A Stack Overflow Developer Survey mostra que 62% dos desenvolvedores se frustram com dívida técnica. O Extract Method não elimina a dívida, mas reduz o custo de trabalhar com ela semana a semana. É a diferença entre uma base de código que você teme abrir e uma que você consegue navegar sem pedir ajuda.
O processo concreto:
Identifique um bloco com propósito único dentro de uma função longa (mais de 20 linhas é um bom sinal)
Extraia para uma função com nome descritivo que revele a intenção
Passe as variáveis locais necessárias como parâmetros
Execute os testes existentes para confirmar que o comportamento não mudou
Adicione um teste unitário para a nova função extraída
Um engenheiro que chega novo no time consegue entender uma função de 8 linhas com nome descritivo em 30 segundos. Uma função de 80 linhas com blocos comentados leva 15 minutos e ainda deixa dúvidas.

Substituir Condicional por Polimorfismo
Cadeias longas de if/elif/else ou switch statements que verificam um campo de tipo são um dos padrões mais comuns que tornam o código difícil de estender. A correção é substituir o condicional por uma hierarquia de classes ou um padrão de estratégia. Cada caso vira uma classe própria com a mesma interface.
O sinal de que chegou a hora de aplicar essa técnica: você está adicionando um novo caso ao switch e precisou modificar código em quatro lugares diferentes. O padrão de estratégia resolve isso porque cada novo caso é uma nova classe, não uma modificação do código existente.
Pule essa técnica se o condicional tiver dois branches e seja improvável que cresça. Polimorfismo tem overhead: você agora tem múltiplos arquivos onde tinha uma função. O retorno só aparece em escala, quando o número de casos cresce com o produto.
Um exemplo prático: um sistema de pagamentos que começa com cartão de crédito e boleto. Quando chega Pix, depois carteira digital, depois criptomoeda, o if/elif se torna ingerenciável. Uma classe base PaymentMethod com subclasses independentes permite adicionar cada novo método sem tocar no código existente.
Análise de Hotspot: Onde Refatorar Primeiro
Essa é a parte que a maioria dos guias ignora, e é onde está o maior ganho estratégico.
Combine complexidade ciclomática com frequência de alteração. Um módulo pode ser genuinamente ruim, mas sem modificações há três anos. Refatorá-lo é arqueologia, não engenharia. Os módulos que custam caro são os que estão bagunçados E nos quais o time mexe toda semana.
A fórmula prática em dois passos:
Liste os 20 arquivos com maior complexidade ciclomática usando uma ferramenta como
radon(Python),complexity-report(JS) ou os linters do seu ecossistemaCruze com os 20 arquivos mais alterados nos últimos 90 dias:
git log --since='90 days ago' --name-only --pretty=format: | sort | uniq -c | sort -rn | head -20
A interseção é a sua lista de trabalho real. Um arquivo com score de complexidade 25 e 47 commits nos últimos 90 dias custa muito mais ao time do que um arquivo com score 40 que ninguém tocou desde 2023.
O BCG 2024 reporta ROI 3x maior para refatoração sistemática versus ad-hoc. Esse número faz sentido quando você para de refatorar por intuição e começa por dados. A análise de hotspot é o dado.

Branch-by-Abstraction: Refatorando Sistemas em Produção
Refatorar um sistema que está em produção e sendo alterado ativamente é diferente de refatorar código legado estável. O risco principal são os long-lived branches de refatoração que ficam semanas divergindo do main até que o merge vira um pesadelo.
A técnica Branch-by-Abstraction resolve isso sem criar branches de longa duração.
O processo tem quatro passos:
Crie uma interface ou abstração que encapsula o comportamento atual
Mova todos os callers para usar a abstração (o código antigo ainda está por baixo)
Escreva a nova implementação por trás da abstração
Faça o switch: a abstração agora aponta para a nova implementação
Remova o código antigo
Em cada passo, o sistema está funcionando e pode ser deployado. Não existe um momento em que tudo está quebrado esperando a refatoração terminar.
É assim que o padrão strangler fig funciona no nível de serviço. A diferença é que Branch-by-Abstraction opera dentro de um único serviço, enquanto strangler fig migra entre serviços. O princípio é o mesmo: manter o sistema funcional em cada passo intermediário.
A condição para usar: você tem testes de integração cobrindo o comportamento que vai mudar. Sem testes, você não tem como garantir que o switch para a nova implementação não quebrou nada.
Substituir Magic Number por Constante Nomeada
Constantes nomeadas não são preferência de estilo. São um mecanismo de fonte única da verdade. Quando a constante muda, muda em todos os lugares, automaticamente.
# Antes
if retry_count > 3:
raise TimeoutError()
if response_time > 500:
log.warning("slow response")
# Depois
MAX_RETRY_ATTEMPTS = 3
SLOW_RESPONSE_THRESHOLD_MS = 500
if retry_count > MAX_RETRY_ATTEMPTS:
raise TimeoutError()
if response_time > SLOW_RESPONSE_THRESHOLD_MS:
log.warning("slow response")O segundo exemplo parece trivial até o dia que você precisa mudar esses números em 14 lugares espalhados por 6 arquivos. O desenvolvedor que modifica o limite de retry talvez não saiba que o mesmo número 3 aparece no módulo de autenticação com significado diferente. Constantes nomeadas tornam a intenção explícita.
Uma variante menos óbvia: strings mágicas. if status == "active": espalhado em 8 arquivos é o mesmo problema. if status == UserStatus.ACTIVE: é a solução.

Introduzir Parameter Object
Uma função que recebe seis argumentos é uma função que vai ser chamada errada. A ordem dos parâmetros vai ser confundida. Um parâmetro booleano vai ser passado como positional em vez de keyword. O caller vai omitir um parâmetro opcional e descobrir semanas depois que o default errado causou um bug em produção.
Quando um grupo de parâmetros sempre viaja junto, eles pertencem a um objeto.
# Antes
def create_report(start_date, end_date, user_id, format, include_drafts, timezone):
...
# create_report(start, end, user, "pdf", False, "UTC") <- legível? não.
# Depois
@dataclass
class ReportConfig:
start_date: date
end_date: date
user_id: str
format: str
include_drafts: bool = False
timezone: str = "UTC"
def create_report(config: ReportConfig):
...
# config = ReportConfig(start_date=start, end_date=end, user_id=user, format="pdf")
# create_report(config) <- autodocumentadoO objeto tem outra vantagem: você pode adicionar um novo parâmetro com valor default sem quebrar nenhum caller existente. Com seis argumentos posicionais, cada nova adição é um risco de breaking change.
A regra prática: se uma função tem mais de 3 parâmetros que frequentemente aparecem juntos em múltiplos callers, introduza um Parameter Object.
A Mentalidade de Refatoração Preparatória
Refatore imediatamente antes de adicionar uma feature, não como projeto separado. Kent Beck resume bem: "make the change easy, then make the easy change". A gestão não precisa aprovar uma iniciativa de refatoração quando ela está embutida no trabalho de feature.
Na prática, isso significa: antes de implementar qualquer nova funcionalidade, passe 15-20 minutos olhando os arquivos que você vai tocar. Se a complexidade ciclomática estiver acima de 10 em qualquer função que você vai modificar, aplique Extract Method primeiro. Você não refatora tudo, refatora o caminho que você vai percorrer.
Essa abordagem tem uma vantagem de gestão pouco discutida: você nunca precisa justificar um sprint de refatoração. A pergunta "quando vamos refatorar o módulo X?" tem uma resposta concreta: "quando adicionarmos a próxima feature que toca nele". O trabalho de refatoração está amarrado a entrega de valor, não a iniciativas abstratas de qualidade.
A McKinsey reporta 40-50% de aceleração na conclusão de projetos de modernização sistemática versus ad-hoc. O ponto importante é que "sistemático" não significa projetos dedicados. Significa consistência no ritmo, não na escala.
O que as Ferramentas de IA Fazem (e Não Fazem) na Refatoração
Cursor, GitHub Copilot e Cody reduzem o atrito na refatoração mecânica. Eles aplicam Extract Method com precisão, renomeiam variáveis de forma consistente em todo o arquivo, geram testes unitários para funções recém-extraídas e sugerem Parameter Objects quando veem muitos argumentos numa função.
O que elas não fazem:
Não dizem onde refatorar (não têm acesso ao histórico de git por padrão)
Não raciocinam sobre tendências de complexidade ao longo do tempo
Não sabem quais arquivos o time modifica com mais frequência
Não entendem o contexto de produto que torna um módulo crítico
Um engenheiro com Cursor ainda precisa do hotspot analysis. A ferramenta acelera a execução, não substitui o diagnóstico. O risco de confundir os dois: você usa IA para refatorar código que não deveria ser tocado agora, enquanto o hotspot real continua custando caro.
O uso mais produtivo: rode o hotspot analysis para identificar o alvo, então use as ferramentas de IA para executar as refatorações mecânicas no arquivo identificado. O estratégico é humano. O tático pode ser assistido.
Por Onde Começar na Segunda-Feira
Rode o hotspot analysis no repositório esta semana. Liste os 20 arquivos com maior complexidade, cruze com os 20 mais alterados nos últimos 90 dias via git. Escolha o arquivo no topo da interseção.
Aplique Extract Method nas três funções mais longas desse arquivo. Escreva testes para cada função extraída. Substitua quaisquer magic numbers por constantes nomeadas. Commit com uma mensagem que referencia o score de complexidade inicial.
Meça o score de novo em 30 dias. Se o número de bugs nesse módulo caiu, ou se as code reviews nele ficaram mais rápidas, você tem evidência concreta. Se não caiu, você aprendeu algo sobre qual métrica realmente importa para o seu time.
Isso não é um projeto de refatoração. É um hábito de engenharia que se constrói um arquivo por vez.