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ê):
- Criou uma suíte de testes para o código inicial.
- Simplificou um pouco o algoritmo e rodou os testes, para ver se não tinha quebrado nada.
- 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.
- 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).
- Criou as subclasses ContractCalculator, que passava false como parâmetro para o construtor da sua superclasse e HourlyCalculator, que passava true.
- Modificou a instanciação dos testes para usar as subclasses.
- Moveu a implementação do método calculate, ainda usando isHourlyWorker, para as subclasses.
- Tornou abstrato o método calculate da superclasse PayCalculator.
- Modificou a implementação do método calculate nas subclasses, só deixando o código correspondente as responsabilidade de cada subclasse.
- 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.
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!

1 Comentário