Introdução: Por que refatorar código legado em C# é uma necessidade
Todo desenvolvedor .NET mais cedo ou mais tarde enfrenta código legado. Não é necessariamente um projeto de 20 anos no .NET Framework 1.1 — muitas vezes, "legado" se torna o código escrito há um ano com pressa, sem testes e violando os princípios SOLID. Trabalhar com esse código se torna um pesadelo: qualquer alteração quebra a funcionalidade em lugares inesperados, e entender a lógica só é possível através de debug.
Refatoração não é reescrever do zero. É melhorar a estrutura interna do código sem alterar seu comportamento externo. Neste artigo, vamos abordar técnicas práticas de refatoração de código legado em C# que podem ser aplicadas imediatamente, minimizando riscos.
1. Preparação: A regra de ouro da refatoração
Antes de alterar o código, certifique-se de que você tem uma "rede de segurança" — um conjunto de testes automatizados. Sem eles, a refatoração se torna um palpite.
Criação de testes de caracterização (characterization tests)
Se não houver testes, escreva testes de caracterização (também conhecidos como golden master ou snapshot tests). A ideia é simples: execute o método com diferentes entradas, registre o resultado e, em seguida, verifique se após a refatoração o resultado não mudou.
// Exemplo de teste de caracterização para um método legado[Test]public void CalculateTotal_ShouldReturnSameResult_AfterRefactoring(){ // Arrange var order = new Order { Items = new List<Item> { new Item { Price = 100, Quantity = 2 } } }; var expected = 200; // Resultado previamente conhecido
// Act var result = LegacyOrderCalculator.CalculateTotal(order);
// Assert Assert.AreEqual(expected, result);}Dica: Use bibliotecas como Verify ou ApprovalTests para testes de snapshot automatizados.
2. Técnica nº1: Extrair método (Extract Method)
A técnica mais simples e eficaz. Quando você vê um método de 200 linhas com comentários como "// aqui cálculo de desconto" — é um candidato ideal.
Exemplo: Método grande
public decimal ProcessOrder(Order order){ decimal total = 0; foreach (var item in order.Items) { total += item.Price * item.Quantity; }
// Cálculo de desconto if (order.Customer.IsPremium) { total *= 0.9m; // 10% de desconto } else if (order.TotalAmount > 1000) { total *= 0.95m; // 5% de desconto }
// Aplicação de imposto total *= 1.2m; // 20% de imposto
return total;}Após a refatoração
public decimal ProcessOrder(Order order){ decimal subtotal = CalculateSubtotal(order.Items); decimal afterDiscount = ApplyDiscount(subtotal, order.Customer); return ApplyTax(afterDiscount);}
private decimal CalculateSubtotal(IEnumerable<Item> items){ return items.Sum(item => item.Price * item.Quantity);}
private decimal ApplyDiscount(decimal total, Customer customer){ if (customer.IsPremium) return total * 0.9m; if (total > 1000) return total * 0.95m; return total;}
private decimal ApplyTax(decimal amount){ return amount * 1.2m;}Vantagens:
- Cada método tem uma única responsabilidade
- O código se torna legível e testável
- A lógica de desconto pode ser reutilizada em outros lugares
3. Técnica nº2: Substituir lógica condicional por polimorfismo
Código legado frequentemente contém longas cadeias de if-else ou switch. Isso viola o princípio aberto/fechado (OCP).
Exemplo: Switch por tipo de documento
public decimal CalculateShippingCost(Order order){ switch (order.ShippingType) { case "Standard": return 5; case "Express": return 15; case "Overnight
```