Prólogo
Se você, como eu, conheceu Ruby através do rails, certamente iniciou seu aprendizado com o livro agile web development with rails. Depois de brincar um pouco com Rails, você por ora começou a ficar mais interessado em Ruby. Ruby com certeza é uma ótima linguagem, mas leva um tempo relativamente maior que o usual para programar efetivamente.
Programar efetivamente em Ruby significa conhecer muito bem a programação de objetos e meta-objetos, ou melhor, meta-programação. Esse novo tipo de programação tem seus pontos fortes e pontos fracos, assim como qualquer outra coisa em software.
Tenha em mente que meta-objetos é um tipo de meta-programação. No entanto, meta-programação não é somente meta-objetos. Para deixar este texto mais limpo, vou explicar detalhadamente ao decorrer do artigo.
Enfim, muitos dizem que a meta-programação é a mágica do Ruby, eu até concordo, bem… parcialmente…
Receitas de um desastre
Indubitavelmente, um projeto com excessiva meta-programação é um projeto confuso, complexo e irritante. Aquelas mensagens de ArgumentError, NameError, que uma vez foram tão claras, já não começam a ser mais explicativas. A razão disso? é simples, estas mensagens são geradas em pontos muito profundos do código.
Meta-programação envolve uma série de chamadas em tempo de execução/avaliação, geralmente envolvendo uma ou diversas, tendo como inputs, o resultado de outras chamadas, avaliações de sub-códigos, classes programáveis e claro, inputs dos próprios desenvolvedores. Muitas vezes, esses inputs somados a natureza imprevisível de um código em tempo de execução, e certos comportamentos, resultam numa perfeita receita para o desastre.
Quando a meta-programação começa a ter uma extensão mais profunda, o pessoal logo nome-a como DSLs (Domain Specific Language(s)). Neste caso, algo mais interessante acontece comparado a programação usual. Neste momento, a meta-programação passa a ter conhecimento e controle sobre a linguagem e o domínio ao qual está sendo aplicado.
Se sua linguagem de programação suporta mecanismos de circularidade, isto é, reflexão, macros, templates, meta-objetos ou qualquer outro tipo de coisa que possa ser avaliável ou gere funcionalidade que a linguagem compreenda, então, muito provavelmente você poderá criar DSLs. Repare que a meta-programação envolve o uso de um ou vários desses recursos citados acima.
Em teoria isso é perfeito, no entanto, o mundo em prática nos reserva grandes surpresas. Como descrito anteriormente, erros gerados em meta-programação são erros gerados nas profundezas dos códigos. São erros que não fazem sentido, são erros complicados de debugar e navegar.
Não apenas isso, a medida em que se usa descontroladamente recursos de meta-objetos, reflexão, macros, o desenvolvedor passa a perder interatividade com a linguagem. A linguagem passa a perder interatividade consigo mesma. Como resultado, a meta-programação começa a inferir novos desafios ao desenvolvedor.
Acredite se quiser, a coisa mais desafiante quando meta-programando é lidar com um sistema razoável de avaliação. Isso sempre faz-me pensar cinco vezes antes de usar qualquer recurso de meta-programação.
Lado negro da feitiçaria
Embora argumentáveis, gostaria que o leitor assumisse essas duas afirmações como verdades:
- DSL é uma nova linguagem construida no topo de uma outra linguagem;
- DSL toma o mecanismo de avaliação da linguagem base como seu;
Desenvolvedores, em sua maioria, não dão muita importância a essas coisas. Eles colocam confiança demais na linguagem de base e esquecem o domínio. Lembrem-se, DSLs são tudo sobre domínio, por sua vez, domínio não é quase nada sobre linguagem.
Embora não seja elegante ou ético apontar projetos de companheiros Rubystas como casos malsucedidos, as vezes se faz necessário. É como dizem por aí, “entrou na água, é para se molhar”.
O projeto que eu ponho em discussão é rSpec. Para quem não conhece, rSpec, é uma biblioteca de testes, assim como o tão popular test/unit do ruby.
Minha recomendação, como desenvolvedor, que já usou e usa meta-programação em diversos projetos. Projetos criados por mim e por outras pessoas, é fazer uso desses recursos somente quando realmente necessário.
Desde que comecei a mexer com software, tenho um amigo que sempre martelava em minha cabeça sobre manter as coisas simples. Diga-se de passagem, demorei muito para aprender isso.
Desde então, sigo uma linha muito direta e extremista de raciocínio. Se as coisas podem ser feitas simplesmente, não vale o esforço tentar sofistica-las. Simples assim.
Tenha em mente esse pedaço de código em rSpec:
describe Bowling do before(:each) do @bowling = Bowling.new end it 'should score 0 for gutter game' do 20.times { @bowling.hit(0) } @bowling.score.should == 0 end end
Agora com a biblioteca padrão de testes:
class BowlingTestCase < Test::Unit::TestCase def setup @bowling = Bowling.new end def test_score_0_for_gutter_game 20.times { @bowling.hit(0) } assert_equal @bowling.score, 0 end end
Será que justifica codificar 10.000 linhas de meta-programa para cumprir apenas uma função estética? Isto é, trocar class BowlingTestCase por describe Bowling? Será que DSLs valem mais de 3.000 commits num repositório? será?
É claro que rSpec não compreende apenas isso. É possível usar um monte de outras funcionalidades. De qualquer forma, rSpec tornou testes, uma tarefa trivial, que é realizado simplesmente, em algo muito sofisticado.
Conclusão
Se a meta-programação não for muito bem planejada e desenvolvida, problemas com avaliações, erros, comportamentos inesperados, tolenadas de código mágicos serão alguns dos desafios. Para contornar esses desafios, o desenvolvedor tem de programar sistematicamente e acima de tudo, entender profundamente cada ponto do sistema e do domínio de forma que possa criar um sistema de avaliação apropriado.
Se minhas palavras têm sido duvidosas, sugiro que o leitor dê uma olhada no repositório do rSpec no rubyforge.
Antes de mergulhar, devemos pensar se realmente vale a pena tornar tarefas tão triviais (como testar pedaços de código) em algo tão elaborado. Devemos pensar se isso realmente vai nos trazer algum avanço ou produtividade como resultado final.