Introdução: quando o código se torna legado
Todo desenvolvedor C# que trabalha em uma grande empresa, mais cedo ou mais tarde, se depara com projetos que têm 5, 10 ou até 15 anos. Esse código é chamado de legado. Ele funciona, gera dinheiro, mas cada alteração nele é um risco. Falta de testes, alto acoplamento (tight coupling), classes gigantescas com milhares de linhas, strings mágicas e código copiado e colado — essa é a realidade do legado.
Uma simples refatoração "de olho" não funciona aqui. São necessárias estratégias avançadas que permitam melhorar o código gradualmente, sem quebrar a lógica de negócios. Neste artigo, abordaremos 4 abordagens principais para refatorar sistemas legados em C# (.NET) em nível avançado.
1. Estratégia "Strangler Fig": substituição de módulos sem parar o sistema
O padrão Strangler Fig (figueira estranguladora) é um clássico para substituição gradual de componentes legados. A ideia: você não reescreve todo o sistema de uma vez, mas gradualmente intercepta as chamadas aos módulos antigos e as redireciona para os novos.
Como implementar em C#
A maneira mais simples é usar interfaces e fábricas. Suponha que você tenha uma classe antiga OldOrderProcessor, que é usada em todo o projeto.
// Interface antiga (já existe)public interface IOrderProcessor{ void Process(Order order);}
// Implementação antiga (legado)public class OldOrderProcessor : IOrderProcessor{ public void Process(Order order) { // Código que funciona há muitos anos, mas é bagunçado }}Criamos uma nova implementação e uma fábrica com feature toggle:
public class NewOrderProcessor : IOrderProcessor{ public void Process(Order order) { // Código novo e limpo }}
public class OrderProcessorFactory{ public IOrderProcessor Create() { // Exemplo: alterna conforme a configuração if (ConfigurationManager.AppSettings["UseNewProcessor"] == "true") return new NewOrderProcessor(); return new OldOrderProcessor(); }}Agora você pode ativar o novo processador para 5% dos usuários, monitorar erros e aumentar gradualmente a porcentagem. Quando o código antigo não for mais necessário, remova-o.
2. Isolamento do núcleo: Separation of Concerns através de Dependency Injection
O código legado frequentemente sofre de God-classes (classes-deus) e dependência direta do banco de dados, sistema de arquivos, APIs externas. A introdução de Dependency Injection (DI) é o primeiro passo para a modularidade.
Exemplo prático
Imagine que você tem uma classe ReportGenerator que cria um SqlConnection internamente:
public class ReportGenerator{ public void Generate() { using (var conn = new SqlConnection("...")) { // consultas } }}Isso é impossível de testar sem um banco de dados real. Refatoração:
public interface IDataRepository{ IEnumerable<DataRow> GetReportData();}
public class ReportGenerator{ private readonly IDataRepository _repository;
public ReportGenerator(IDataRepository repository) { _repository = repository; }
public void Generate() { var data = _repository.GetReportData(); // geração do relatório }}Agora você pode substituir o repositório por um mock em testes de unidade. Use o contêiner DI padrão do .NET Core (Microsoft.Extensions.DependencyInjection) ou o Autofac para injeção de dependências.
3. Teste de código legado: Golden Master e Characterization Tests
Sem testes, refatorar é como pular de paraquedas sem reserva. Para código legado, a abordagem ideal são os Characterization Tests (testes de caracterização) ou Golden Master Testing.
Como funciona
Você executa