Neste sábado o Raphael Molesim e eu, podres depois de enfrentar o Ágiles 2009 e a noite de Floripa, resolvemos resolver um Kata proposto pelo Uncle Bob, e fazer um Coding Dojo de fatoração. O Raphael propôs fazermos em Ruby com RSpec, já que, segundo ele, não dá pra fazer Coding Dojo sem RSpec (deixa ele ir na próxima reunião do .Net Architects que mostramos que ele está enganado).
Enfim, resolvemos o problema em pouco menos de uma hora e depois fizemos uma retrospectiva, conferindo como o Uncle Bob havia resolvido o problema dele, já que ele postou um Powerpoint da solução.
Algumas conclusões da retrospectiva:
- A solução do Uncle Bob foi muito mais simples que a nossa, já que resolvemos antes o problema dos números primos, algo que ele não fez (algo que não era necessário, dependendo de como você encara o problema).
- O Ruby realmente permite programar sem IDE, mas uma IDE decente faz muita falta, principalmente para quem não conhece muito o Ruby ou as APIs do RSpec. Um Notepad anabolizado, como o GEdit, ou o Notepad++, chega só até uma distância, e não é suficiente.
- RSpec traz uma maneira muito legal de fazer testes. O código fica com uma elegância invejável.
- O runner do RSpec não permite rodar só o teste que você quiser, e isso não é legal.
- A performance no IronRuby é sofrível. Chega a ser 50 vezes mais lento que o MRI.
Vejam o que fizemos. Aqui estão os testes do fatorador, que é a classe que faz a fatoração.
require 'fatorador' describe "Motor calculo fatoracao" do it "deve saber ser criado" do Fatorador.new end it "deve retornar 0 se for informado o número 0" do fatorador = Fatorador.new numeros = fatorador.fatorar 0 numeros[0].should == 0 end it "deve retornar 1 se for informado o número 1" do fatorador = Fatorador.new numeros = fatorador.fatorar 1 numeros[0].should == 1 end it "deve retornar 2 se for informado o número 2" do fatorador = Fatorador.new numeros = fatorador.fatorar 2 numeros[0].should == 2 end it "deve retornar 2,2 se for informado o número 4" do fatorador = Fatorador.new numeros = fatorador.fatorar 4 numeros[0].should == 2 numeros[1].should == 2 end it "deve retornar 5 se for informado o número 5" do fatorador = Fatorador.new numeros = fatorador.fatorar 5 numeros[0].should == 5 end it "deve retornar 2,3 se for informado o número 6" do fatorador = Fatorador.new numeros = fatorador.fatorar 6 numeros[0].should == 2 numeros[1].should == 3 end it "deve retornar 2,2,5 se for informado o número 20" do fatorador = Fatorador.new numeros = fatorador.fatorar 20 numeros[0].should == 2 numeros[1].should == 2 numeros[2].should == 5 end it "deve retornar 37 se for informado o número 37" do fatorador = Fatorador.new numeros = fatorador.fatorar 37 numeros[0].should == 37 end it "deve retornar 7,7 se for informado o número 49" do fatorador = Fatorador.new numeros = fatorador.fatorar 49 numeros[0].should == 7 numeros[1].should == 7 end it "deve retornar 2,163 se for informado o número 326" do fatorador = Fatorador.new numeros = fatorador.fatorar 326 numeros[0].should == 2 numeros[1].should == 163 end it "deve retornar 3 10 vezes se for informado o número 59049" do fatorador = Fatorador.new numeros = fatorador.fatorar 59049 numeros.size.should == 10 numeros.each {|n| n.should == 3} end it "deve retornar 3,5,7 se for informado o número 105" do fatorador = Fatorador.new numeros = fatorador.fatorar 105 numeros[0].should == 3 numeros[1].should == 5 numeros[2].should == 7 end end
Algumas observações sobre este código:
- Erramos na definição de fatoração, que não devia incluir o um. Mas não consertei depois, esse foi o código gerado entre o Raphael e eu, sem a correção.
- Um programador experiente de Ruby vai perceber que não somos fluentes na linguagem, mas foi divertido fazer este código.
Depois fizemos um coding dojo com Brian Marick, que foi o keynote speaker do Ágiles 2009, além de ser um dos autores do Agile Manifesto. Com ele aprendemos mais algumas coisas de Ruby e teste com Shoulda. Demos sorte de ele estar no aeroporto, de termos quase uma hora de espera do vôo e de ele ser super simpático. Depois eu vi um kata do Uncle Bob e percebi que podia ficar melhor ainda.
Seguindo em frente, o código da classe foi esse:
require 'Fixnum' class Fatorador def fatorar numero return 0 if numero == 0 if numero.primo? [numero] else numeros = [] primo = 1 numero_sendo_divido = numero begin primo = primo.proximo_primo while numero_sendo_divido % primo == 0 numero_sendo_divido = numero_sendo_divido / primo numeros << primo end end while (primo < numero_sendo_divido) numeros end end end
Notem que ele checa se o número é primo. A rigor isso não é necessário pra bater a meta, que é descobrir os números que fatoram outro número, mas acabamos fazendo isso também, fomos além da meta. Dessa forma, ele evita tentar dividir um número por outro que não é primo, como tentar dividir 15 por 10, por exemplo (outras otimizações ainda teriam que ser feitas pra ficar melhor). Para isso, alteramos a classe Fixnum, que no Ruby define inteiros. É como se fosse a classe System.Int32 do .Net.
Os testes de Fixnum foram esses:
require 'Fixnum' describe "Numeros primos" do describe "primos" do it "deve retornar falso se for informado 0" do 0.primo?.should be_false end it "deve retorno verdadeiro se for informado 1" do 1.primo?.should be_true end it "deve retorno falso se for informado 4" do 4.primo?.should be_false end it "deve retorno falso se for informado 6" do 6.primo?.should be_false end it "deve retorno verdadeiro se for informado 13" do 13.primo?.should be_true end it "deve retorno falso se for informado 21" do 21.primo?.should be_false end it "deve retorno verdadeiro se for informado 37" do 37.primo?.should be_true end it "deve retorno falso se for informado 49" do 49.primo?.should be_false end end describe "proximo primo" do it "deve conseguir me entregar 3 quando informado 2" do primo = 2 proximo_primo = primo.proximo_primo proximo_primo.should == 3 end it "deve conseguir me entregar 5 quando informado 3" do primo = 3 proximo_primo = primo.proximo_primo proximo_primo.should == 5 end end end
E aqui está a classe em si:
class Fixnum def primo? return false if self == 0 for i in 2..(self/2) do return false if self % i == 0 end true end def proximo_primo proximo = self while true proximo = proximo + 1 return proximo if proximo.primo? end end end (Uma coisa um pouco irritante no Ruby é que ele não tem o operador “++”, ou seja, ou você usa “+= 1” ou faz como fizemos, “proximo = proximo + 1”.)
O download do código está aqui. Pra rodar isso tudo em IronRuby você precisa baixar a gem rspec com:
igems install rspec
E colocar os arquivos ispec e ispec.bat no diretório bin do IronRuby (normalmente c:\ruby\bin), já que estes arquivos não são criados na instalação do RSpec. Estes arquivos foram criados por mim depois que eu apanhei um pouco para descobrir como fazer pra criar, se você usa o MRI não vai precisar deste passo extra. Espero que o IronRuby passe a criá-los automaticamente até a versão 1.0, ou ao menos disponibilize uma maneira de criarmos estes arquivos de maneira mais simples, ou um diretório.
Pra rodar o RSpec é muito simples, basta chamar “ispec” e o nome do arquivo sendo testado:
ispec fatorador_spec.rb ispec fixnum_spec.rb Aqui vocês vêem os resultados dos testes:
Simples, expressivo e interessante, não é?
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.