Ruby – DRY, Refactoring e Otimização
Ruby – DRY, Refactoring e Otimização
Este artigo é todo baseado no 2º exercício da 5ª aula de um curso online de Ruby (http://rubylearning.org/) que estou fazendo (junho de 2008), turma FORPC101-5C, na verdade baseado na minha solução para o exercício, se você é ou foi aluno deste curso pode acompanhar a thread desta solução no fórum do curso, neste link http://rubylearning.org/class/mod/forum/discuss.php?d=575&parent=9715
Como ja expus aqui, considero este curso surpreendente, nunca é demais repetir, jamais pensei que um dia elogiaria um curso online, mas realmente este é de tirar o chapéu, para quem, como eu, gosta do tipo de metodologia em que se valoriza a participação e a prática tanto quanto a teoria, acredito que este curso se encaixa como uma luva. Bem, chega de rasgação de seda e vamos a mais uma ponderação sobre alguns tópicos de Ruby. Nesta 5ª semana do curso (a duração é de 8 semanas) aprendi muito com muito pouco, eu explico, com um problema ridículo pude aprender sobre refactoring, otimização e o conceito DRY em Ruby. Dito isto vamos ao “game”: O 2º exercício da 5ª semana é o seguinte:
Escreva a classe Rectangle. O uso da mesma deve ser feito da seguinte forma:
r = Rectangle.new(23.45, 34.67) puts 'Area is = ' + r.area().to_s puts 'Perimeter is = ' + r.perimeter.to_s
Ou seja, criar uma instancia da classe e apresentar na tela a área e o perímetro do retângulo. A solução, digamos… óbvia, é a que a todos imediatamente pensaram: Escrever uma classe que receba 2 parâmetros em seu método de inicialização, crie 2 variáveis de instancia com estes parâmetros, esta classe possuiria 2 métodos, um para calcular área e outro para calcular o perímetro. Todos tiveram a mesma idéia e produziram praticamente o mesmo código, comigo não foi diferente, sabendo que a área de um triangulo é base*altura e que o perímetro é 2*(base+altura), produzi o seguinte código:
class Rectangle def initialize(b, h) @base = b.to_f @height = h.to_f end
def area return @base*@height end
def perimeter return 2*@base+2*@height end end
r = Rectangle.new(23.45, 34.67) puts 'Area is = ' + r.area().to_s puts 'Perimeter is = ' + r.perimeter.to_s
| Iniciantes: |
| 1 – Initialize é o método ‘construtor’ das classes em Ruby |
| 2 – A palavra chave def define um método |
| 3 – Em uma classe um nome de variável precedida pelo sinal de arroba (@) indica que é uma variável de instância. |
| 4 – puts apresenta uma string na tela |
Ok, foi a solução encontrada por todos, mas no fórum, no thread sobre o exercício, um dos membros dos professores levantou as seguintes questões
“O grupo deveria considerar algumas coisas não demonstradas nestas soluções. Após a instancia da classe Rectangle ser criada – e a menos que o triangulo seja “redimensionado” – a área e o perímetro sempre serão os mesmos. Na verdade são como propriedades da instancia do retângulo. Assim sendo, porque recalcular a área e o perímetro todas as vezes em que é preciso obte-los? … “http://rubylearning.org/class/mod/forum/discuss.php?d=575&parent=9451 “… ”
“… Mas o que é o estado de um retângulo? Anteriormente vocês consideraram o estado do retângulo como sendo a base e altura, agora vocês estão considerando a área e o perímetro. Mas o que define um retângulo?..” http://rubylearning.org/class/mod/forum/discuss.php?d=575&parent=9463
Tornando mais OO
Estas questões intrigaram os participantes e surgiram várias piadinhas, eheheh. Bem, acabei atentando para a observação das execuções repetidas dos métodos e resolvi fazer uma classe onde o usuário da mesma pudesse ter acesso a área e ao perímetro através de variáveis de instancia, desta maneira teríamos apenas um método para os cálculos que seria chamado apenas uma vez, internamente pela classe, mas com meu parco conhecimento de ruby como fazer para que o usuário tenha acesso às variáveis de instancia? Para quem já programava em outra linguagem OO isto é muito simples, basta pensar da mesma forma, em Java ou em Delphi bastaria criar um método para acessar estas variáveis, o que se convencionou chamar em Java de Getters and Setters methods, mas por via das dúvidas resolvi consultar a documentação (fantástico a quantidade de docs disponíveis) e acabei descobrindo que em Ruby isto é ainda mais simples, podemos criar variáveis de instancia como sendo atributos acessíveis através do atalho attr_reader, quando pesquisava a documentação descobri também que para que um método não seja acessível fora da classe basta colocar a palavra private antes da definição do método, desta maneira todos os métodos definidos em seguida serão privados até que se encontre a palavra public, minha nova versão do código ficou assim:
class Rectangle attr_reader :area, :perimeter def initialize(b, h) @area, @perimeter = 0.0, 0.0 calcs(b.to_f, h.to_f) end private def calcs(b,h) @area = b*h @perimeter = 2*(b+h) end end r = Rectangle.new(23.45, 34.67) puts r.area puts r.perimeter
|
Iniciantes: Pode-se fazer atribuições paralelas em ruby, é o que faço na primeira linha do método initialize, exemplo: a, b = 1,2 seria o mesmo que a atribuição em 2 linhas, assim: a = 1 b = 2 |
Nada mal, agora economizamos um método, mas olhando atentamente percebi que dava para melhorar ainda mais, para que o método calcs? Porque não fazer todos os cálculos no método initialize e ainda deixar o usuário ter acesso as variáveis base e altura do triangulo? Então criei o código abaixo e o postei no fórum:
class Rectangle attr_reader :area, :perimeter, :height, :base def initialize(b, h) @base = b.to_f @height = h.to_f @area = @base*@height @perimeter = 2*(@base+@height) end end r = Rectangle.new(23.45, 34.67) puts "Rectangle Properties:\n" puts 'Base: ' + r.base.to_s puts 'Height: ' + r.height.to_s puts 'Area: ' + r.area.to_s puts 'Perimeter: ' + r.perimeter.to_s
Refatorando e melhorando
Agora parece que ficou mais “cool”, quando a instância da classe é criada e o método initialize (toda classe em ruby dever ter este método que funciona como método construtor da classe) é executado, todos os cálculos necessários são feitos e o usuário da classe terá pleno acesso a todas as “propriedades” ou o “estado” do triangulo através das variáveis de instancia. Mas olhando o código no fórum (http://rubylearning.org/class/mod/forum/discuss.php?d=575&parent=9715), fiquei pensando que desta forma a classe estava muito engessada, não havia possibilidade do usuário redimensionar o triangulo foi então que decidi refatorar mais uma vez adicionando um método chamado resize, vamos ao código:
class Rectangle attr_reader :area, :perimeter, :height, :base def initialize(b, h) @base = b.to_f @height = h.to_f calcs end def resize(b, h) @base = b.to_f @height = h.to_f calcs end private def calcs @area = @base*@height @perimeter = 2*(@base+@height) end end
Aplicando o conceito DRY
Legal, agora se pode ter acesso a todas as propriedades do triangulo e ainda redimensioná-lo, o código parece perfeito, mas olhando atentamente o mesmo fere um conceito muito caro aos gurus do Ruby, este conceito chama-se DRY (Don’t Repeat Yourself), veja que há duas linhas que se repetem na classe, as duas primeiras do método initialize são repetidas no inicio do método resize, calcs é chamado no método initialize e também no método privado calcs, parece bem óbvio depois que alguém aponta, mas quando estamos programando parece que o afã de ver o resultado final bloqueia nossa visão para estes detalhes, se não fosse o conceito DRY martelando no meu cérebro, com certeza eu não atentaria para isto. Bem, depois destas observações arregacei as mangas e produzi o código final:
class Rectangle attr_reader :area, :perimeter, :height, :base def initialize(b, h) resize(b,h) end def resize(b, h) @base = b.to_f @height = h.to_f calcs end private def calcs @area = @base*@height @perimeter = 2*(@base+@height) end end r = Rectangle.new(23.45, 34.67) puts "Rectangle Properties:\n" puts 'Base: ' + r.base.to_s puts 'Height: ' + r.height.to_s puts 'Area: ' + r.area.to_s puts 'Perimeter: ' + r.perimeter.to_s puts "*" * 30 r.resize(53.45, 44.67) puts "New Rectangle Properties:\n" puts 'Base: ' + r.base.to_s puts 'Height: ' + r.height.to_s puts 'Area: ' + r.area.to_s puts 'Perimeter: ' + r.perimeter.to_s puts "*" * 30
Veja que agora as variáveis de instância são criadas no método resize e não mais no initialize, para isto chamo resize no initialize, como o método resize chama calcs, mais uma vez evito a repetição. Desta forma esta classe fica praticamente perfeita.
Mais refactoring
Eu disse perfeita? não! praticamente, né? depois que achava que já tinha acabado este artigo, voltei ao forum e tive a grata surpresa de um novo refactoring de um dos participantes -Jeff Hales- Gente finissima, em sua explanação ele explica que não há razão para o métdo calcs, veja o que ele fez com a classe, realmente um refactoring matador:
<span style="font-weight: bold; color: #9966cc;">class</span> Rectangle attr_reader :area, :perimeter, :height, :base <span style="font-weight: bold; color: #9966cc;">def</span> initialize<span style="font-weight: bold; color: #006600;">(</span>b, h<span style="font-weight: bold; color: #006600;">)</span> size<span style="font-weight: bold; color: #006600;">(</span>b,h<span style="font-weight: bold; color: #006600;">)</span> <span style="font-weight: bold; color: #9966cc;">end</span> <span style="font-weight: bold; color: #9966cc;">def</span> size<span style="font-weight: bold; color: #006600;">(</span>b,h<span style="font-weight: bold; color: #006600;">)</span> @base = b.<span style="color: #9900cc;">to_f</span> @height = h.<span style="color: #9900cc;">to_f</span> @area = @base*@height @perimeter = <span style="color: #006666;">2</span>*<span style="font-weight: bold; color: #006600;">(</span>@base+@height<span style="font-weight: bold; color: #006600;">)</span> <span style="font-weight: bold; color: #9966cc;">end</span> <span style="font-weight: bold; color: #9966cc;">alias</span> resize size <span style="font-weight: bold; color: #9966cc;">end</span>
O único “porém” neste refactoring apontado pelo professor José Carlos Monteiro e que não tem como não concordar é que este aliás é absoultamente desnecessário, além do ‘drop’ deste aliás será que dá para fazer mais alguma coisa para melhorar esta classe? quem se habilita?
June 13th, 2008 at 11:16 am
Muito bom!!
PS.: Triângulo Retângulo rsrsrsr