Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danilobatistaqueiroz/82eacddd6777b35d60871eee97c7fdf3 to your computer and use it in GitHub Desktop.
Save danilobatistaqueiroz/82eacddd6777b35d60871eee97c7fdf3 to your computer and use it in GitHub Desktop.
Refactoring | Mente iniciante

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.

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!

Duplicação e dependência

Dependência é o principal problema em desenvolvimento de software. Duplicação é o sintoma.
Mas, ao contrário da maioria dos problemas na vida, nos quais eliminar os sintomas faz com que um problema mais grave apareça em outro lugar, eliminar duplicação nos programas elimina dependência.

Código Limpo

Bons programadores com certeza já investiram algum tempo limpando seu próprio código. Eles fazem isso porque aprenderam que código limpo é mais fácil de mudar do que código complexo e bagunçado, e bons programadores sabem que raramente eles escrevem código limpo logo de cara.

Complexidade é débito

Não se preocupar com o design do seu código é como entrar em dívida.
No mundo dos negócios, a maioria das empresas precisa de algum débito para poder funcionar eficientemente.
No entanto, com a dívida vem o pagamentos de juros que, em software, é o custo adicional de manutenção e extensão causada por código demasiadamente complexo.
Você pode suportar um pouco de juros, mas se os pagamentos se tornam muito grandes, você ficará sobrecarregado.
É importante gerenciar a sua dívida, pagando-a aos poucos através de refatoração.

Bibligrafia

Martin Fowler, em Refactoring Kent Beck, em TDD by Example

Referencias

http://thecleancoder.blogspot.com/2010/09/too-small-to-refactor.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment