Uncle Bob blogou no Clean Coder sobre uns code reviews que ele fez do código (e das refatorações) de um cara chamado John MacIntyre.

Uncle Bob criticou a conclusão do cara de que refatoração não vale a pena para projetos reais: refatoração é uma maneira efetiva de evitar que o código apodreça e fique difícil de manter.

O que achei de mais interessante sobre o post do Uncle Bob é que ele pegou o código inicial do cara e foi fazendo pequenas refatorações, no estilo Baby Steps. No final, acabou melhorando drasticamente a estrutura do código e tornando-o muito mais extensível. O exemplo é bem pequeno mas, mesmo assim, houve uma melhora significativa.

O código inicial:

package payroll;

public class PayCalculator {
  public static float calculate(double hours,
                                double rate,
                                boolean isHourlyWorker) {
    if (hours < 0 || hours > 80) {
      throw new RuntimeException("Hours out of range: " + hours);
    }
    float wages = 0;

    if (hours > 40) {
      float overTimeHours = hours - 40;
      if (!isHourlyWorker) {
        wages += (overTimeHours * 1.5) * rate;
      } else {
        wages += overTimeHours * rate;
      }
      hours -= overTimeHours;
    }
    wages += hours * rate;
    return wages;
  }
}

O código depois da refatoração:

package payroll;

public class ContractorCalculator extends PayCalculator {
    @Override
    protected double determinePay(double hours, double rate) {
        return hours * rate;
    }
}

public class HourlyCalculator extends PayCalculator {
    @Override
    protected double determinePay(double hours, double rate) {
        double overtime = Math.max(0, hours - 40);
        return hours * rate + overtime * rate * 0.5;
    }
}
package payroll;

public abstract class PayCalculator {
    public final double calculate(double hours, double rate) {
        validateHours(hours);
        return determinePay(hours, rate);
    }

    protected void validateHours(double hours) {
        if (hours < 0 || hours > 80) {
            throw new RuntimeException("Hours out of range: " + hours);
        }
    }

    protected abstract double determinePay(double hours, double rate);
}

Para refatorar o código original, imagino que Uncle Bob tomou os seguinte passos (de bebê):

  1. Criou uma suíte de testes para o código inicial.
  2. Simplificou um pouco o algoritmo e rodou os testes, para ver se não tinha quebrado nada.
  3. Livrou-se do boolean isHourlyWorker do método calculate, colocando-o como variável de instância da classe PayCalculator e passando o booleano no construtor. Isso fez com que os testes não compilassem.
  4. Modificou a instanciação de PayCalculator nos testes, para que compilassem. Isso ficou meio estranho, o que expôs que haviam duas classes derivadas da classe PayCalculator (uma com o parâmetro do construtor true e outra false).
  5. Criou as subclasses ContractCalculator, que passava false como parâmetro para o construtor da sua superclasse e HourlyCalculator, que passava true.
  6. Modificou a instanciação dos testes para usar as subclasses.
  7. Moveu a implementação do método calculate, ainda usando isHourlyWorker, para as subclasses.
  8. Tornou abstrato o método calculate da superclasse PayCalculator.
  9. Modificou a implementação do método calculate nas subclasses, só deixando o código correspondente as responsabilidade de cada subclasse.
  10. Removeu o booleano isHourlyWorker do construtor da classe PayCalculator e removeu a chamada das subclasses a esse construtor.

A cada passo, os testes são rodados. Parece muito complicado para um código tão pequeno, mas é muito flúido programar desse jeito. Lembra o trabalho de um sushi man que, a cada movimento, deixa sua faca e outros utensílios limpos e organizados.

O código ficou muito mais extensível: para criar um outro tipo de PayCalculator, eu não preciso modificar o código da classe PayCalculator. Através de polimorfismo e classes abstratas, criamos um ponto de extensão para o que tinha mais sentido de negócio. A esse modificar por extensão deram o nome de Open/Closed Principle. Uma métafora interessante desse princípio, que é mostrada na figura abaixo, é que para colocar um casaco você não precisa fazer uma cirurgia para abrir o peito.

Figura de brinquedo de cirurgia de peito aberto com os dizeres "Open Chest Surgery Is Not Needed When Putting On A Coat"
Por Tom Dalling

Uma observação: métodos que recebem booleanos são mais difíceis de ler e de usar, porque em geral o programador tem que olhar o código do método para realmente saber o que o booleano faz. E usar booleanos para desviar o fluxo do código mostra que o método está fazendo duas coisas,  mais do que deveria. Além disso, se você quiser criar uma terceira coisa, você tem que trocar o booleano por um inteiro, Enum, ou coisa parecida e modificar o código do método.

Conforme foi dito em um comentário do post do Clean Coder, o código de Uncle Bob teve uma duplicação talvez desnecessária: a chamada do método validateHours()  em ambas as subclasses, tanto na ContractCalculator quanto na HourlyCalculator. Quem fez esse comentário colocou uma sugestão de código no Gist, que foi o que reproduzi acima.

No livro Refactoring, Martin Fowler faz algo parecido, pegando um código pseudo-OO, com classes anêmicas e toda a lógica em um só método,  e fez pequenas refatorações em , até tornar o código muito mais simples e modificável. Coloquei o código do exemplo no meu github.

Mas, afinal de contas, o que que tem a ver o título deste post?

É que acredito começar de um código estruturado, modificando-o em pequenos passos, até chegar a um código extensível e com as responsabilidades bem definidas, é uma boa maneira de ver a verdadeira utilidade de OO. E de aprender!