Pular para o conteúdo
Categoria: Fundamentos & Boas Práticas15 min de leitura

O que é refatoração e quando aplicar

Por Schematize Blog ·

Aprenda a melhorar a estrutura interna do código sem mudar seu comportamento, com técnicas seguras, exemplos práticos, trade-offs e os sinais que pedem refatoração.

Refatorar é uma das habilidades que mais separam um desenvolvedor iniciante de um experiente. Não se trata de reescrever tudo nem de "limpar o código quando der tempo": é uma disciplina precisa, com técnicas próprias e regras de segurança. Neste artigo você vai entender o que é refatoração de verdade, quando aplicá-la, como reconhecer os sinais de que o código precisa melhorar e como fazer isso sem quebrar nada.

O que é refatoração

Martin Fowler, autor do livro de referência sobre o tema, define refatoração de forma cirúrgica: é o processo de alterar a estrutura interna de um software sem mudar seu comportamento externo observável (Fowler, 2018). Em outras palavras, o programa continua fazendo exatamente a mesma coisa do ponto de vista de quem o usa; o que muda é a organização interna do código.

Essa definição tem duas consequências importantes:

    Manter essa fronteira nítida é o que torna a refatoração segura. Quando você sabe que o comportamento não deveria mudar, qualquer mudança de comportamento detectada por um teste é imediatamente um sinal de erro.

    Vale notar uma sutileza na palavra "refatoração". No uso casual, qualquer faxina de código é chamada de "refatorar". Fowler, porém, usa o termo num sentido estrito: cada refatoração é uma transformação pequena e nomeada que preserva comportamento. A diferença é prática. Quando você diz "vou refatorar este módulo" e passa três dias mexendo em tudo ao mesmo tempo, você não está refatorando no sentido disciplinado — está reescrevendo, e reescrevendo é uma atividade de risco muito maior. A refatoração de verdade avança em micro-passos reversíveis.

    Refatoração não é o mesmo que reescrita

    Reescrita joga fora o código existente e começa do zero. Refatoração transforma o código existente de dentro para fora, mantendo-o funcional a cada passo. A reescrita é tentadora porque dá a ilusão de um recomeço limpo, mas costuma subestimar o conhecimento embutido no código antigo — todos aqueles if estranhos que tratam casos de borda reais descobertos ao longo de anos. Refatorar preserva esse conhecimento enquanto melhora a forma.

    Por que refatorar

    Todo código tende a degradar com o tempo. Cada ajuste apressado, cada exceção tratada de última hora, cada "só mais um if" deixa o sistema um pouco mais bagunçado. A esse acúmulo damos o nome de dívida técnica, e a refatoração é a forma de pagá-la.

    Os principais ganhos de refatorar regularmente:

      Refatoração é, no fim, um investimento na sua própria velocidade futura — e na de quem vai herdar o código. É uma das práticas centrais do O que é Clean Code? Guia completo de código limpo, que trata a melhoria contínua como parte do ofício, não como luxo.

      Há uma metáfora financeira útil aqui. A dívida técnica, como a dívida bancária, cobra juros: cada vez que você toca num trecho confuso, paga um pouco mais caro do que pagaria se ele estivesse limpo. Pequenas refatorações contínuas funcionam como pagar a fatura todo mês; adiar acumula juros até o ponto em que o time inteiro fica lento e cada mudança vira um drama. O objetivo não é zerar a dívida — algum nível de imperfeição é normal e saudável — mas mantê-la sob controle.

      Code smells: os sinais de que algo precisa mudar

      Fowler popularizou o termo code smell — "mau cheiro de código": indícios na superfície que sugerem um problema mais profundo. Smell não é bug; é um sinal de alerta que merece investigação. Os mais comuns:

        Reconhecer smells é metade do trabalho. A outra metade é aplicar a técnica certa para removê-los. Um detalhe importante: um smell é um convite à investigação, não uma ordem automática de refatorar. Há contextos em que um método longo é, de fato, a forma mais clara de expressar uma sequência linear de passos. O julgamento continua sendo seu.

        Técnicas de refatoração mais comuns

        O catálogo de Fowler tem dezenas de refatorações nomeadas. Algumas são tão frequentes que vale memorizar:

        Extrair função (Extract Function)

        A refatoração mais usada de todas. Você pega um trecho coeso de código e o transforma em uma função com nome descritivo.

        Antes:

        function imprimirResumo(pedido) {
          let total = 0;
          for (const item of pedido.itens) {
            total += item.preco * item.quantidade;
          }
          // aplica imposto
          total = total * 1.1;
        
          console.log(`Total: ${total}`);
        }

        Depois:

        function calcularSubtotal(itens) {
          return itens.reduce((soma, item) => soma + item.preco * item.quantidade, 0);
        }
        
        function aplicarImposto(valor) {
          return valor * 1.1;
        }
        
        function imprimirResumo(pedido) {
          const total = aplicarImposto(calcularSubtotal(pedido.itens));
          console.log(`Total: ${total}`);
        }

        O comportamento é idêntico, mas agora cada operação tem nome e pode ser testada e reusada isoladamente.

        Renomear (Rename)

        Trocar um nome ruim por um claro é refatoração de pleno direito. d vira diasRestantes; proc() vira processarPagamento(). Nomes bons são documentação que não envelhece.

        Substituir condicional por polimorfismo

        Quando um switch decide comportamento por tipo, frequentemente é melhor substituí-lo por subclasses ou estratégias. Veja a transformação:

        // Antes: condicional decide comportamento por tipo
        function calcularFrete(envio) {
          switch (envio.tipo) {
            case "expresso": return envio.peso * 5 + 20;
            case "padrao":   return envio.peso * 3;
            case "retirada": return 0;
          }
        }
        // Depois: cada tipo encapsula seu próprio cálculo
        class FreteExpresso { calcular(peso) { return peso * 5 + 20; } }
        class FretePadrao   { calcular(peso) { return peso * 3; } }
        class FreteRetirada { calcular()     { return 0; } }

        Essa refatoração é a ponte natural para vários Design Patterns essenciais que todo dev deveria conhecer, como Strategy e State, que costumam emergir desse movimento.

        Introduzir parâmetro objeto

        Vários parâmetros que sempre andam juntos (dia, mes, ano) podem virar um único objeto (Data), encurtando assinaturas e dando coesão. O mesmo vale para latitude/longitude virando Coordenada, ou valor/moeda virando Dinheiro.

        Substituir número mágico por constante

        Um valor literal solto no meio do código, como 1.1 ou 86400, esconde sua intenção. Extrair para uma constante nomeada (ALIQUOTA_IMPOSTO, SEGUNDOS_POR_DIA) torna o código autoexplicativo e centraliza a mudança caso o valor precise variar.

        Decompor condicional

        Uma expressão booleana complexa dentro de um if fica mais legível quando extraída para uma função com nome:

        // Antes
        if (data.getMonth() >= 11 || data.getMonth() <= 1) { aplicarTarifaInverno(); }
        
        // Depois
        if (ehAltaTemporada(data)) { aplicarTarifaInverno(); }

        A regra de ouro: refatore com testes

        Aqui está a parte mais importante. Como você garante que "não mudou o comportamento"? Com testes. A refatoração segura depende de uma rede de proteção que avisa imediatamente se algo quebrou.

        O ciclo é sempre o mesmo:

          Sem essa rede, "refatorar" vira "reescrever e torcer". Por isso a refatoração é inseparável de uma boa suíte — vale a leitura do guia de Testes automatizados: o guia definitivo para começar antes de encarar mudanças estruturais maiores. Robert C. Martin coloca a coisa de forma contundente: sem testes, você não tem coragem para limpar o código, e sem limpeza o código apodrece (Martin, 2008).

          E quando não há testes?

          Esse é o cenário mais comum em código legado, e existe uma resposta para ele. Michael Feathers chama de código legado justamente todo código sem testes, e propõe uma técnica de duas fases: primeiro você identifica os "pontos de costura" (seams) — lugares onde é possível inserir um teste sem grandes mudanças — escreve testes de caracterização que apenas documentam o comportamento atual (mesmo que esteja errado), e só então refatora com segurança (Feathers, 2004). Testes de caracterização não julgam se o comportamento é correto; eles registram o que o sistema faz hoje, para que você perceba qualquer alteração.

          Refatoração e controle de versão

          Um aliado silencioso da refatoração é o commit pequeno e frequente. Como cada passo preserva o comportamento e os testes passam, é seguro commitar a cada micro-melhoria. Isso dá dois benefícios: se um passo der errado, você desfaz apenas ele com um simples reset, sem perder horas de trabalho; e o histórico fica legível, separando claramente "refatoração" de "nova feature". Uma boa prática é nunca misturar, no mesmo commit, mudança de comportamento e mudança de estrutura — quem revisar agradece.

          Quando refatorar (e quando não)

          Refatoração não é um evento isolado no calendário; ela acontece em fluxo com o trabalho normal. Fowler propõe alguns gatilhos práticos:

            E quando não refatorar?

              Trade-offs: o lado difícil da decisão

              Refatorar tem custos reais que vale enxergar com honestidade. O principal é o custo de oportunidade: cada hora gasta reorganizando código é uma hora que não foi gasta entregando valor visível ao usuário. Isso cria tensão com prazos e com gestores que não veem a melhoria interna. A resposta madura não é refatorar tudo nem nunca refatorar, mas sim integrar a melhoria ao trabalho de feature, em pequenas doses, de modo que ela quase não apareça como item separado de cronograma.

              Outro trade-off é o risco de introduzir bugs. Ironicamente, a atividade que deveria reduzir defeitos pode criá-los se feita sem testes ou em passos grandes demais. Daí a insistência nos micro-passos: cada um é tão pequeno que o erro, quando ocorre, é fácil de localizar.

              Por fim, há o risco da over-engineering: refatorar em direção a abstrações elaboradas que o sistema ainda não precisa. Aplicar polimorfismo onde um simples if bastaria torna o código mais difícil, não mais fácil. A regra prática é refatorar para resolver dor real e presente, não para antecipar flexibilidade hipotética.

              Um exemplo completo passo a passo

              Vale ver uma refatoração se desenrolando do começo ao fim, no ritmo certo de micro-passos. Suponha esta função, que mistura cálculo, formatação e regra de negócio:

              function gerarFatura(cliente) {
                let resultado = `Fatura de ${cliente.nome}\n`;
                let total = 0;
                for (const item of cliente.consumos) {
                  let valor = 0;
                  if (item.tipo === "horario") {
                    valor = item.minutos * 0.5;
                  } else if (item.tipo === "dados") {
                    valor = item.megabytes * 0.1;
                  }
                  total += valor;
                  resultado += `  ${item.tipo}: R$ ${valor}\n`;
                }
                resultado += `Total: R$ ${total}\n`;
                return resultado;
              }

              Passo 1 — extrair o cálculo do item. O trecho do if/else calcula o valor de um item; vamos isolá-lo. Rode os testes depois.

              function valorDoItem(item) {
                if (item.tipo === "horario") return item.minutos * 0.5;
                if (item.tipo === "dados")   return item.megabytes * 0.1;
                return 0;
              }

              Passo 2 — usar a nova função no loop. A função gerarFatura agora chama valorDoItem(item) em vez do bloco condicional. Testes verdes? Seguimos.

              Passo 3 — separar cálculo de formatação. Note que o loop faz duas coisas: soma o total e monta o texto. Extraímos o cálculo do total:

              function calcularTotal(consumos) {
                return consumos.reduce((soma, item) => soma + valorDoItem(item), 0);
              }

              Ao final, gerarFatura fica enxuta e cada peça é testável isoladamente. Repare que nunca houve um momento em que o código estivesse quebrado: cada passo preservou o comportamento, e os testes confirmaram isso. Esse é o ritmo da refatoração disciplinada — pequeno, verde, commit, repete.

              Refatoração e complexidade

              Uma forma objetiva de saber se a refatoração valeu a pena é medir a complexidade do código. Métricas como a Complexidade ciclomática: como medir e reduzir a complexidade do código contam os caminhos de decisão de uma função: quanto mais ifs, loops e ramificações, mais difícil de testar e entender. Extrair funções e substituir condicionais por polimorfismo tende a reduzir esse número — dando a você um indicador mensurável do progresso.

              Essa conexão também ajuda a priorizar: as funções com maior complexidade são geralmente as melhores candidatas a refatoração, porque concentram risco e dificuldade. E manter cada unidade com uma só responsabilidade conversa diretamente com os O que é SOLID? Os 5 princípios do design orientado a objetos, especialmente o da Responsabilidade Única.

              Erros comuns ao refatorar

              Mesmo desenvolvedores experientes tropeçam em armadilhas previsíveis:

                Perguntas frequentes

                Refatoração precisa estar no backlog como tarefa separada? Nem sempre. A maior parte da refatoração saudável acontece embutida no trabalho de feature, em micro-doses (a regra do escoteiro). Refatorações grandes e estruturais, que levam dias, sim, merecem ser planejadas e comunicadas como item próprio, com justificativa clara de retorno.

                Dá para refatorar sem testes automatizados? Tecnicamente sim, mas com risco muito maior. Em código legado sem testes, o caminho recomendado é primeiro escrever testes de caracterização para o trecho afetado e só então refatorar. Refatorar "no escuro" só se justifica em mudanças triviais e localizadas, como renomear uma variável local.

                Qual a diferença entre refatoração e otimização de performance? Otimização busca mudar uma propriedade observável — velocidade ou uso de memória. Refatoração, por definição, preserva o comportamento observável, incluindo performance aproximada. As duas atividades podem se ajudar (código limpo é mais fácil de otimizar), mas têm objetivos distintos.

                Com que frequência devo refatorar? Continuamente, em pequenas doses, sempre que tocar num trecho de código. A refatoração contínua é muito mais barata e segura do que grandes mutirões periódicos de limpeza.

                Conclusão

                Refatorar é melhorar a estrutura interna do código preservando seu comportamento — nem mais, nem menos. É uma disciplina, não uma faxina ocasional: acontece em passos pequenos, protegida por testes, integrada ao fluxo de desenvolvimento. Aprender a reconhecer code smells, dominar algumas refatorações fundamentais como extrair função e renomear, e nunca mexer no que não está coberto por testes são os pilares dessa prática.

                O retorno é silencioso, mas decisivo: um código que permanece fácil de entender e barato de mudar ao longo dos anos. Times que refatoram continuamente pagam a dívida técnica em parcelas pequenas; times que adiam acabam pagando de uma vez, com juros, em forma de reescritas dolorosas. Comece pela regra do escoteiro, garanta seus testes e melhore o código um pouco a cada vez que passar por ele.

                Referências

                  Leituras relacionadas

                  Nenhum comentário ainda

                  Seja o primeiro a comentar.

                  Deixe seu comentário

                  Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

                  Entrar com Canverly