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:

  1. 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.
  2. 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:

Testes com RSpec rodando no IronRuby

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.