Refatoração: quando parar de adicionar features e começar a limpar o código

1. O Ciclo da Dívida Técnica: Como a Pressão por Features Acumula Bagunça

1.1 O paradoxo da produtividade

No início de um projeto, cada nova feature parece simples. O código é enxuto, as dependências são poucas e a equipe entrega rapidamente. Com o tempo, porém, a base de código cresce e a velocidade de entrega diminui. Esse é o paradoxo da produtividade: quanto mais features você adiciona sem refatorar, mais lentas se tornam as entregas futuras.

1.2 Sinais clássicos de código "podre"

Três indicadores são particularmente perigosos:

  • Complexidade ciclomática alta: métodos com muitas ramificações condicionais
  • Duplicação de código: a mesma lógica replicada em múltiplos lugares
  • Acoplamento excessivo: uma classe depende de dezenas de outras para funcionar

1.3 A metáfora da dívida

Ward Cunningham comparou a dívida técnica a uma dívida financeira. Adiar a refatoração é como pagar apenas os juros: você continua entregando, mas cada vez mais devagar. Se não pagar o principal, o sistema quebra.

2. Indicadores Objetivos: Quando o Código Grita por Refatoração

2.1 Métricas de código

Três métricas são essenciais para decisões objetivas:

  • Coesão (Cohesion): uma classe deve ter uma única responsabilidade. Baixa coesão indica que ela faz muitas coisas diferentes
  • Acoplamento (Coupling): classes com muitas dependências externas são frágeis a mudanças
  • Complexidade de McCabe: mede o número de caminhos independentes em um método. Valores acima de 10 são um alerta

2.2 Cheiros de código (Code Smells)

Alguns padrões são facilmente identificáveis:

  • God Class: uma classe que faz tudo
  • Long Method: métodos com mais de 30 linhas
  • Shotgun Surgery: uma mudança simples exige alterações em vários lugares

2.3 A regra do Boy Scout

A regra é simples: "Deixe o código mais limpo do que você o encontrou." Isso se torna obrigatório quando:

  • Você precisa modificar um trecho para adicionar uma feature
  • O tempo para entender o código já supera o tempo para implementar a mudança
  • A equipe gasta mais de 30% do sprint apenas entendendo código existente

3. O Dilema do "Feature Freeze": Parar ou Não Parar?

3.1 Refatoração contínua vs. sprints dedicados

A refatoração contínua é mais sustentável: a cada alteração, você melhora um pequeno trecho. Sprints dedicados de limpeza podem ser necessários quando a dívida técnica está crítica, mas exigem negociação com stakeholders.

3.2 Negociando com stakeholders

Para convencer a equipe de negócios, use argumentos quantitativos:

  • "Este trecho de código está causando 40% dos bugs reportados"
  • "Refatorar reduzirá o tempo de entrega da próxima feature em 50%"
  • "O custo de manutenção atual é 3x maior que o custo de refatorar"

3.3 A técnica do Strangler Fig

Em vez de parar completamente as features, você pode aplicar o padrão Strangler Fig: gradualmente, substitua partes do sistema legado por novas implementações, sem interromper o fluxo de entregas.

// Exemplo de Strangler Fig: substituir gradualmente
// Versão antiga (legado)
public class PagamentoAntigo {
    public void processar(double valor) {
        // lógica antiga e complexa
    }
}

// Nova implementação (substituição gradual)
public class PagamentoNovo {
    public void processar(double valor) {
        // lógica nova e simplificada
    }
}

// Roteador que direciona gradualmente
public class PagamentoRouter {
    public void processar(double valor) {
        if (featureToggle.isAtivo("novo-pagamento")) {
            new PagamentoNovo().processar(valor);
        } else {
            new PagamentoAntigo().processar(valor);
        }
    }
}

4. Estratégias Práticas de Refatoração Sem Riscos

4.1 Operações atômicas seguras

As operações mais seguras são pequenas e testáveis:

  • Extrair método: transformar um bloco de código em um método separado
  • Renomear variável: dar nomes que expressem a intenção
  • Mover classe: realocar funcionalidade para a classe correta

4.2 Testes como rede de segurança

Nunca refatore sem testes. Se o código não tiver cobertura, escreva testes de caracterização primeiro:

// Teste de caracterização antes de refatorar
public class CalculadoraTest {
    @Test
    public void testSoma() {
        // Teste que captura o comportamento atual
        int resultado = new Calculadora().soma(2, 3);
        assertEquals(5, resultado);
    }
}

4.3 Refatoração orientada a padrões

Substituir condicionais aninhadas por polimorfismo é uma das refatorações mais poderosas:

// Antes: condicionais aninhadas
public double calcularFrete(String tipo, double peso) {
    if (tipo.equals("normal")) {
        return peso * 1.5;
    } else if (tipo.equals("expresso")) {
        return peso * 3.0;
    } else if (tipo.equals("internacional")) {
        return peso * 5.0 + 10.0;
    }
    return 0;
}

// Depois: polimorfismo com Strategy
public interface FreteStrategy {
    double calcular(double peso);
}

public class FreteNormal implements FreteStrategy {
    public double calcular(double peso) { return peso * 1.5; }
}

public class FreteExpresso implements FreteStrategy {
    public double calcular(double peso) { return peso * 3.0; }
}

public class FreteInternacional implements FreteStrategy {
    public double calcular(double peso) { return peso * 5.0 + 10.0; }
}

5. Quando a Refatoração NÃO é a Resposta

5.1 Refatorar código que será descartado

Se um trecho de código será substituído em breve, refatorá-lo é desperdício. A pergunta-chave é: "Este código ainda estará aqui daqui a 3 meses?"

5.2 A tentação da reescrita total

Reescrever do zero raramente funciona. O novo sistema geralmente replica os mesmos erros, e o custo de migração é subestimado. A abordagem incremental é quase sempre melhor.

5.3 Refatoração sem testes

Refatorar sem testes é como fazer cirurgia com os olhos vendados. Se você não pode verificar se o comportamento foi preservado, está apenas quebrando o código de forma mais organizada.

6. O Papel da Cultura de Equipe na Manutenção do Código Limpo

6.1 Code reviews focados em qualidade

Code reviews devem ir além da lógica: questione a coesão, o acoplamento e a complexidade. Pergunte: "Este método poderia ser extraído?" e "Esta classe tem responsabilidade demais?"

6.2 Responsabilidade coletiva

A regra do Boy Scout deve ser um compromisso da equipe, não individual. Quando todos melhoram o código que tocam, a dívida técnica diminui continuamente.

6.3 Automatizando a detecção

Ferramentas como linters e analisadores estáticos devem rodar no CI/CD:

# Configuração de CI para detectar code smells
# .github/workflows/quality.yml
name: Code Quality
on: [push]
jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run linter
        run: npm run lint
      - name: Run complexity analysis
        run: npx complexity-report src/

7. Checklist Decisório: Um Fluxo para Saber se é Hora de Refatorar

7.1 Perguntas-chave

Antes de refatorar, responda:

  1. Este código será alterado nos próximos 3 meses?
  2. A refatoração reduzirá o tempo de entrega da próxima feature?
  3. Existem testes que cobrem este trecho?
  4. A refatoração pode ser feita em pequenos passos?

7.2 Matriz risco vs. retorno

Priorize trechos com:

  • Alto impacto na produtividade da equipe
  • Baixo risco de quebra (boa cobertura de testes)
  • Alta frequência de alterações

7.3 Exemplo prático

// Código analisado: método com complexidade alta
public class ProcessadorPedidos {
    public void processar(Pedido pedido) {
        if (pedido.getTipo().equals("VIP")) {
            if (pedido.getValor() > 1000) {
                // lógica para VIP com valor alto
            } else {
                // lógica para VIP com valor baixo
            }
        } else if (pedido.getTipo().equals("normal")) {
            if (pedido.getValor() > 500) {
                // lógica para normal com valor alto
            } else {
                // lógica para normal com valor baixo
            }
        } else {
            // lógica padrão
        }
    }
}

// Decisão: refatorar usando polimorfismo
// Motivos:
// - Este código é alterado a cada sprint (alta frequência)
// - Complexidade ciclomática = 4 (acima do ideal)
// - Existem testes unitários que cobrem todos os cenários
// - A refatoração pode ser feita em 3 passos seguros

Referências