Programar em Ruby te faz odiar linguagens estáticas

Papo interessante que estava tendo com o Fabrício ainda agora…

fabrício diz:
hehehhe
vc tb ta estudando ruby ?
Phillip Calçado - All that’s sacred..comes from youth! diz:
sim

fabrício diz:
pq ?

Phillip Calçado - All that’s sacred..comes from youth! diz:
por que eh uma linguagem *muito* boa
produtiva e de qualidade

fabrício diz:
rapaz..
hehe
sacanagem, mas eu quero saber o q eh q vcs acharam tao extraordinario nessa linguagem
eu vi aquele videozinho

Phillip Calçado - All that’s sacred..comes from youth! diz:
closures, dinamismo, DSLs

fabrício diz:
realmente me surprendi

Phillip Calçado - All that’s sacred..comes from youth! diz:
hoje eu estava com um problema interessante em java

fabrício diz:
qual ?

Phillip Calçado - All that’s sacred..comes from youth! diz:
tem uma classe qualquer que cria um objeto que pode ser considerado uma sessão com o banco de dados
só que numa jvm só podem existir duas sessões ao mesmo tempo, em threads diferentes
e me foi pedido para toda vez que se tenta abrir uma dessas, logar num arquivo antes quantas existem em memoria
para saber se estavamos tendo muito problema com isso ou se quase nunca duas sessoes ficam abertas
o jeito mais simples seria colocar uma variavel de classes (estatica) na classe que cria a conexão para saber quantas ela criou, certo?

fabrício diz:
é, a principio sim
e aii ?

Phillip Calçado - All that’s sacred..comes from youth! diz:
mas o problema eh que a classe que pede para essa outra criar uma sessão é quem fecha a sessão
então nós poderíamos saber quantas ssessões foram criadas, mas não quando uma for fechada
e essa classe sessão é do fornecedor do banco de dados, ou seja não pode ser alterada
como resolver isso em java?

fabrício diz:
unh … sem alterar a classe do fornecedor do bd, acho q tem q queimar um pouco os neuronios hehe
pq essa classe na pior das solucoes poderia notificar a todos que tem um comunicacao com ela, de que a sessao foi finalizada
sim, e ai?
vc fez o q?

Phillip Calçado - All that’s sacred..comes from youth! diz:
basicamente o que eu fiz rpa contornar a situação foi pegar todas as threads do sistema, ver quais delas possuem sessões abertas e logar, e como isso é bem lento deve ser melhor usar um adapter nesta classe
mas se fosse em uma linguagem como ruby, ao criar a instancia de sessao eu poderia alterar o metodo commit() desta instancia, fazendo ele avisar a classe fabrica que foi fechada antes de chamar o metodo original

Não entendeu a solução? Em Ruby uma classe é dinâmica, você pode alterá-la em runtime. Vamos a um exemplo (você pode testá-lo neste site) . Então supomos que nossa classe Session seja assim:


class Session
    def commit
        puts ‘COMMITED’
    end
end

E que quem crie sessions seja esta fabrica:


class Factory
    def create
        return Session.new
    end
end

Agora vamos modificar a fabrica para que guarde o numero de Sessions abertas e altere o método Session#commit


class Factory
#vamos guardar a quantidade de sessoes aqui
    @count
    

    #cria um ‘getter’ para o count

    attr_reader :count

    

    #construtor
    def initialize
        @count=0
    end
    

    #metodo usado para informar que uma sessao foi finalizada
    def session_ended
        @count=@count-1
    end
    

    def create
        s=Session.new
    

    #muda o metodo do objeto criado e cria variavel de instancia para localizar a factory
    class <<s
    

        #onde vamos guardar uma referencia para nossa factory
        @factory
    

        #cria um ’setter’ para factory
        attr_writer :factory
    

        #Muda o nome do metodo commit real
        alias original_commit commit
    

        def commit
            @factory.session_ended
            #chama o metodo antigo
            return original_commit
        end
    end
    

    #diz à sessao modificada que esta eh sua factory
    s.factory=self
    

    @count=@count+1
    

    return s
    end
end

Ficou bem maior que o método original, mas muito do que foi adicionado são getters, setters e construtores. No fim do post está o texto do teste completo, basta salvar num arquivo e rodar no interpretador ruby.

Note que o código não é thread-safe, teríamos que colocar uns monitores ali. Também é bom notar que não são todas as instâncias de Session que são afetadas mas apenas aquelas criadas pela Factory. Caso fosse necessário, poderíamos fazer com que todas as instâncias d Session fossem alteradas.

É assim que coisas como o Rails são implementadas e por isso é tão complexo criar ‘um Rails em Java’.

Se pudesse fazer isso em Java não estaria quebrando minha cabeça amanhã para otimizar uma ferramenta de diagnóstico… Minhas opções são: (a) empacotar a classe Session num Adapter ou (b) continuar buscando todas as threads e vendo se elas possuem sessões abertas

Provavelmente vou criar um adapter ou se não puder uma subclasse. WrappedSession implements Session e implemento o método commit() fazendo o necessário antes de delegar à Session real. O fato de criar uma classe nova na hierarquia apenas para adicionar um conceito ortognal (ou um cross cutting concern, algo importante para o sistema mas não importante do ponto de vista do usuário) não me faz muito bem. E se eu tiver que acrescentar esta funcionalidade a uma outra subclasse de Session criada por alguma das outras factories? Com wrapper tudo bem, mas se não usar subclasse eu crio outra com a mesma finalidade? E se eu quiser que todas as Sessions, não importa onde foram criadas, tenham esta funcionalidade? Find “Session”/Replace with “WrapperSession”?

Ps: estou tendo problemas com o WYSIWYG do WrodPress, a formatação tem ficado meio bagunçada devido a isso - Com ajuda do Diego isso foi meio que resolvido :P

Exemplo completo:

class Session
    def commit
        puts 'COMMITED'
    end
end
    
class Factory
    
    #vamos guardar a quantidade de sessoes aqui
    @count
    
    #cria um ‘getter’ para o count
    attr_reader :count
    
    #construtor
    def initialize
        @count=0
    end
    

#metodo usado para informar que uma sessao foi finalizada
def session_ended
    @count=@count-1
end
    
    def create
        s=Session.new
        
        #muda o metodo dentro do objeto criado e cria variavel de instancia para localizar a factory
        class <<s
        
            #onde vamos guardar uma referencia para nossa factory
            @factory
            
            #cria um ’setter’ para factory
            attr_writer :factory
        
            #Muda o nome do metodo commit real
            alias original_commit commit
                
    

            def commit
                @factory.session_ended
                #chama o metodo antigo
                return original_commit    

            end
        end
    

    
    #diz à sessao modificada que esta eh sua factory
    
    s.factory=self
    

        @count=@count+1
        

        return s
    end
end
    
f = Factory.new
s = f.create
puts f.count
s.commit
puts f.count

UPDATE: O taq deu uma limpada no código, ficou menor mas se for sua primeira vez vendo algo em Ruby pode ser mais confuso::

class Session
    def commit
        puts "COMMITED"
    end
end

class Factory
    attr_reader :count # pode usar o attr_reader direto que ele cria a @count
    def initialize
        @count = 0 # aqui tem que 'setar' o valor default né, inclusive se você não
    end # tivesse usado o attr_reader, a @count poderia ser criada aqui sem problemas
    def session_ended
        @count -= 1 # olha o -= ao invés do @count = @count - 1
    end
    def create
        s = Session.new
        class << s
        attr_writer :factory # mesmo esquema acima - não precisa explicitar @factory
        alias original_commit commit
        def commit
            @factory.session_ended
            original_commit
        end
    end
        s.factory = self
        @count += 1 # olha o += ao invés do @count = @count + 1
        s # não precisa do return, apesar de ficar mais legível com ele
    end
end

f = Factory.new
s = f.create
puts f.count
s.commit
puts f.count

11 Responses to “Programar em Ruby te faz odiar linguagens estáticas”

  1. Renato says:

    Cara, quando descobri esse lance… é uma das coisas que mais gosto em Ruby…

    Nos meus exemplos de estudo tem uma alteração da classe Array para se comportar como se fosse um endereço IP, muito responsa.
    Então com o require eu ativava ou desativava esse comportamento… Outra coisa muito interessante é alterar as classes dos booleanos:


    class TrueClass
    def to_s
    "Sim"
    end
    end

    class FalseClass
    def to_s
    "Não"
    end
    end

    Massa, né?

    É um lance meio parecido com Aspectos não acham?

  2. leonardootto says:

    Bom num entendi totalmente seu exemplo. Acho que é pq não tenho tanto contato com ruby assim.
    Bom vamos la.
    Se vc pudesse simular um exemplo em java sobre isso como seria?
    valew.

  3. Anonimo says:

    Com AOP voce conseguiria resolver isso , poderia interceptar a chamada do driver do fabricante, nao havendo necessidade de variaveis estaticas.

  4. pcalcado says:

    O que uma engine de AOP faria seria o mesmo que eu, subclasse da Session como um Proxy (que não ia poder ser um DynamicProxy) e monitorá-la.

    Acrescentar uma biblioteca a mais num sistema deste tamanho e complexidade só para fazer um diagnóstico não está nos meus planos ;)

    A solução ficou iterar pelas threads por enquanto. Descobri que não adianta fazer um adapter para a Session porque não é a factory que cria ela (a factory na verdade cria algo chamado virtualtransaction :P)

    Leonardo, não dá para fazer isso em Java. O que eu fiz foi alterar a implementação do método commit() em runtime, em Ruby as classes estão semrpe abertas, é possível alterá-las a qualquer momento, em Java você não pode alterar a implementação de um objeto diretamente logo após instanciá-lo.

  5. Legal as alterações que o Taq fez. Achei que inclusive ficou mais claro e limpo o código :)

  6. leonardootto says:

    Sim eu sei q não é possivel isso em Java.
    E agora tbm já entendi o que vc queria dizer.
    :)

  7. Concordo sobre as facilidades do Ruby e o lance de implementar mixins nativamente é foda. Uma alternativa não tão suja para resolver o seu problema seria implementar um interceptor (à la AOP). Na verdade, o que a gente supõe ser “nativo” no Ruby (este suporte a mixins, por exemplo) poderia ser facilmente implementado para a plataforma Java (como em Groovy, por exemplo) usando Dynamic Proxies. A questão é: quem é macho o bastante para fazer isso? Hehe! Melhor deixarmos para o Guillaume Laforge et al., né?

  8. buy flomax says:

    buy flomax

    buy flomax

  9. EU says:

    Ruby eh uma linguagem pra quem não quer pensar!!!!!!,eporgramr tbm….

  10. RenaTim says:

    é a primeira vez que escrevo Ruby no google pra procurar algo a respeito e cai aqui. Achei bastante interessante. Não conheço nada a repeito de linguagem web. Só Delphi totalmente off-line. Mas queria investir em algo inovador e promissor. É mais negocio que Java?

  11. Realmente, isso é muito bom. Eu estou estudando Ruby agora e há pouco estava fazendo um exemplo onde eu posso adicionar arquivos externos em tempo de execução e mudar o comportamento de uma classe (na real redefinir o método mesmo). No meu caso eu não quero mais o método anterior, após redefinir só o novo interessa, então eu estendi a classe com com o uso de um módulo mesmo (self.extend(NovoModulo)).

    É louco pra caramba, ainda nem consigo imaginar as possibilidades que isso me dá.