Otimização de Tempo

Refactoring contínuo vs. rewrite total

Quando cada estratégia economiza mais recursos

13 de ago. de 2026·Ricardo Coelho

Em abril de 2000, Joel Spolsky publicou um texto argumentando que reescrever código do zero era o pior erro estratégico que uma empresa de software podia cometer, usando a Netscape como exemplo central. A empresa abandonou o código do Netscape 4.0 pra construir a versão 6.0 do zero, e o intervalo de quase três anos entre versões principais entregou o mercado de navegadores à Microsoft sem resistência (Joel Spolsky, 2000). A prescrição absoluta envelheceu. O problema que ela tentava resolver permanece: calcular o custo real de cada alternativa antes de escolher exige reconhecer que refactoring e rewrite carregam perfis de risco distintos, com condições diferentes pra funcionar.

Cada alteração incremental num refactoring preserva o sistema em funcionamento enquanto modifica a organização interna, e o custo de cada passo fica contido pela superfície limitada da mudança. O risco por unidade de trabalho é baixo e o feedback é imediato, até porque o sistema continua rodando entre iterações. O investimento se distribui sem interrupção de entrega ao negócio. Testes continuam validando comportamento enquanto integrações seguem funcionando, e o conhecimento acumulado permanece acessível mesmo quando a arquitetura muda. Martin Fowler descreveu o Strangler Fig Application pra migrações maiores: novos componentes crescem ao redor do sistema legado e absorvem suas responsabilidades até que o original seja removido (Martin Fowler, Strangler Fig). O Strangler Fig distribui risco no tempo. Na prática, tenho visto que ele também mantém rollback disponível em cada etapa, reduzindo o custo político de reverter uma decisão que não funcionou.

Essa abordagem encontra seu limite quando a base de código resiste à mudança incremental. Sistemas com acoplamento profundo entre camadas, onde cada alteração local produz efeitos em cascata em módulos distantes, tornam o preço de cada passo desproporcionalmente alto. O refactoring pressupõe independência entre as partes; quando ela não existe, a coordenação exigida consome o benefício da abordagem. O outro limite é temporal: quando a distância entre a arquitetura atual e a necessária é grande, o caminho incremental leva anos, e manter dois modelos mentais em paralelo (o que o sistema é e o que ele está se tornando) cobra seu preço em velocidade e em carga cognitiva sobre o time.

Um rewrite concentra investimento e risco de uma vez: os resultados só se tornam visíveis quando o novo sistema atinge paridade funcional com o anterior, e o período intermediário produz gasto sem retorno direto. Não existe taxa de fracasso segmentada pra rewrites. Os números que aparecem nessa posição vêm de levantamentos de projetos de software em geral, e a fonte mais citada, o CHAOS Report, tem problemas metodológicos documentados (Eveleens & Verhoef, 2010). O mecanismo de falha que mais encontro nem é técnico. O código antigo contém decisões acumuladas ao longo de anos, muitas não documentadas, codificadas como tratamentos de caso especial e validações de borda cujas peculiaridades só se manifestam em produção. Redescobrir essas decisões é o custo oculto que as estimativas não alcançam. Ferramentas de modernização assistida por IA prometem atacar justamente esse custo, gerando testes de caracterização a partir do comportamento observado em produção e automatizando a análise de dependências. Se entregam o que anunciam, o trabalho de mapear o que o código faz diminui. Mas entender por que ele faz, inclusive quando o bug virou contrato com o usuário, continua sendo humano.

Existem condições sob as quais o rewrite é a decisão de menor custo total. A mais óbvia: a plataforma tecnológica atingiu o fim de vida útil e o ecossistema de suporte degradou a ponto de tornar a manutenção progressivamente mais cara, com o gasto de infraestrutura dupla durante a migração incremental excedendo o investimento do rewrite. Outra condição aparece quando o domínio do negócio mudou de forma que a modelagem original não consegue mais representar, não por limitação de implementação, mas por incompatibilidade entre o modelo de dados e a realidade do negócio. Me parece que o cenário mais subestimado é o de um sistema que precisa atender requisitos não funcionais radicalmente diferentes dos originais, como mudança de escala em ordens de magnitude, quando a arquitetura não foi projetada pra acomodar essa transição.

A reconstrução do cliente desktop do Slack (Electron) ilustra uma abordagem que dissolve a fronteira entre as duas estratégias. Em julho de 2019, Mark Christian e Johnny Rogers descreveram como a equipe reconstruiu o cliente sem descartar o produto em funcionamento, mantendo entrega contínua enquanto substituía componentes internos. Eles chamaram a abordagem de Ship of Theseus (Slack Engineering, 2019). O resultado se aproxima de um rewrite em escopo final, mas executado com o perfil de risco do refactoring: incrementos verificáveis com rollback disponível em cada etapa.

A decisão útil exige uma análise de custo total: risco de execução, custo de oportunidade do período sem entrega, carga cognitiva da transição e probabilidade realista de conclusão no prazo. O refactoring contínuo deve ser o ponto de partida, porque distribui risco e preserva entrega, mas precisa de condição de saída. Quando o gasto incremental de cada refatoração cresce consistentemente e a distância até a arquitetura alvo se mede em anos, ou quando a plataforma ou o modelo de domínio deixou de ser compatível com a realidade, o rewrite entra como opção legítima. A execução deve seguir o Strangler Fig pra migrações de sistema, ou o Branch by Abstraction pra refatorações dentro do mesmo código (Fowler), evitando o salto de fé de escopo fechado. A preferência do engenheiro é irrelevante pra essa decisão.