Refatorar é bom para aprender OO

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.

Open/Closed Principle

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!

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s