DRY e Blocos de Código com Ruby
Posted in ruby, Ruby para iniciantes on February 7th, 2010 by Sergio Azevedo – 12 CommentsO Iuri já mostrou num post anterior, como criar métodos (funções) em ruby. Sendo assim somos praticamente craques em construir funções ruby. Neste post vamos explorar recursos avançados e interessantes das funçoes ruby, para isso, utilizaremos um array com alguns nomes.
Problema Inicial:
Devemos criar um método que seja capaz de percorrer um array, exibindo seus elementos. (Ok, tudo bem nós sabemos que todo array possui um método each. Mas vamos fingir que esse kra não existe)
lista = ["sergio","roberto", "iuri","luiz"] def mostra_elementos( array ) for elemento in array puts elemento end end mostra_elementos(lista)
Ótimo! Atingimos nosso objetivo. O codigo acima exibira os nomes contidos no array.
Os requisitos mudaram:
Agora nós queremos percorrer o array exibindo cada um de seus elementos ao contrario, por exemplo, “sergio” será exibido como “oigres”. Podemos criar um novo método para isso, então vamos ao trabalho:
def mostra_elementos_ao_contrario( array ) for elemento in array puts elemento.reverse end end mostra_elementos_ao_contrario( lista )
Perfeito! Mais uma vez concluímos nossa tarefa.
DRY, mais um princípio de Desenvolvimento de Software:
Os problemas anteriores envolviam operações com elementos de um array (uma lista), e assim acabamos criando dois métodos para resolver os problemas propostos. Existe um princípio de desenvolvimento de software que é focado em reduzir a repetição/duplicação de código, ele é conhecido como: Don’t Repeat Yourself – (DRY). Este principio diz que:
“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
O que poderiamos traduzir como: “Cada parte do conhecimento deve ter uma representação única e não ambigua dentro de um sistema”. Desta forma utilizando o DRY para analisar as funções que escrevemos até agora, podemos notar que a parte do conhecimento de percorrer um array não possui uma representação única em nosso sistema. O ideal seria que o codigo de percorrer um array nao fosse mais repetido, e é exatamente nisso que vamos trabalhar agora.
Aplicando o DRY
A maneira mais simples de resolvemos o problema de duplicação do codigo de percorrer o array, é sem duvida realizar um refactor chamado extract method, e assim isolar a lógica de percorrer arrays em um método. Entao vamos lá:
def percorre_array( array ) for elemento in array end end def mostra_elementos( array ) percorre_array( array) puts elemento end def mostra_elementos_ao_contrario( array ) percorre_array(array) puts elemento.reverse end lista = ["sergio","roberto", "iuri","luiz"] mostra_elementos(lista) mostra_elementos_ao_contrario(lista)
Pronto! Com este método que acabamos de criar eliminamos o problema de repetição de código. Porém ao executar este programa receberemos uma mensagem de erro. O grande problema aqui é que agora os métodos mostra_elementos e mostra_elementos_ao_contrario não conseguem mais acessar cada elemento do array. Estes métodos agora usam a função percorre_array, e somente dentro desta função é que a variavel elemento existe. Neste ponto podemos pensar que, ou fazemos a coisa ficar DRY, ou fazemos o programa funcionar. Calma, com Ruby nos poderemos fazer isso funcionar e ainda ser DRY. Para isso só precisamos aprender a usar os famosos blocos do Ruby.
Ruby block’s
Com certeza você já ouviu, que tudo em ruby é objeto. Na verdade mesmo, quase tudo em Ruby é objeto, por exemplo blocos de código não são objetos. Mas o importante é que em Ruby podemos armazenar trechos de código em um objeto especial que se chama Proc. Veja:
bloco = Proc.new { puts "sou um bloco de codigo" } bloco.call
Acabamos de criar um objeto, que contem código, e fizemos isso utilizando Proc.new e fornecendo o código através das chaves ”{ }”. De posse de uma Proc, podemos executar o código que ela contem a qualquer momento. Basta pra isso usar o método call da Proc. No exemplo acima ao chamar bloco.call veremos o texto sou um bloco de codigo. O interessante disso é que agora podemos passar código como parametro, veja um exemplo:
def faco_o_que_mandar( block ) block.call end bloco = Proc.new {puts "sou um bloco de codigo"} faco_o_que_mandar(bloco) faco_o_que_mandar Proc.new{puts "Mais um bloco"}
Mas podemos fazer como os rubistas fazem na maioria das vezes. Eles chamam a função e passam o bloco como parametro, para isso precisamos fazer uma pequena alteração em nossa função, basta colocar um & a frente do nome do nosso parametro. Outra coisa é que geralmente os rubistas utilizam a plavara yield ao invés de coisas como bloco.call, vamos mudar isso também:
def faco_o_que_mandar( &block ) yield end faco_o_que_mandar {puts "sou um bloco de codigo tipo rubista"} faco_o_que_mandar do puts "blocos do end sao usados quando vc quer" puts "fazer coisas que precisem de mais de uma" puts "linha =)" end
O grande barato é que agora escrevemos uma função que recebe e executa um código “visitante”. Este é um recurso muito poderoso, e é graças a ele que vamos conseguir resolver o problema de percorrer um array para fazer coisas diferentes com seus elementos, sem precisar repetir código.
Tirando vantagem dos blocos
Bem, nós já temos uma função que sabe andar em um array. Agora só precisamos fazer esta função receber um bloco de código “visitante” e assim teremos tudo resolvido. Vamos então ao trabalho:
def percorre_array( array, &block ) for elemento in array yield end end lista = ["sergio","roberto", "iuri","luiz"] percorre_array(lista) { puts elemento } percorre_array(lista) { puts elemento.reverse }
Neste momento você pode estar se perguntando o porquê do bloco de código estar fora dos parênteses. Por mais estranho que possa parecer é desse jeito que funciona mesmo. Os blocos são informados depois dos parametros “convencionais”.
Agora seria ótimo se isso funcionasse, não é? Pena que não vai funcionar. Ao executar esse código veremos o seguinte erro: undefined local variable or method `elemento’ for main:Object (NameError).
Repare no erro, ele diz que a variavel elemento não está definida. Isso aconteceu porque esta variavel é local do método percorre_array, ou seja só existe, e só é visivel naquele método. Nós precisamos manipular esta variavel, para que isto seja possível o método que possui a variavel precisa permitir que os blocos visitantes utilizem esta variavel. Fazer isso é simples, basta que no yield nos passemos a variavel em questão como parâmetro.
Quando colocamos o yield na função é como se dissesemos: “Rode o codigo visitante agora”. E quando colocamos a frente do yield parâmetros é como se dissessemos: “Ei código vistante, se você quiser pode usar estes caras aqui que eu tenho”. Vejamos como fica isso no nosso codigo:
def percorre_array( array, &block ) for elemento in array #agora estamos expondo a variavel elemento # para o codigo visitante yield elemento end end lista = ["sergio","roberto", "iuri","luiz"] # uma pequena alteracao é necessaria aqui percorre_array(lista) { |elemento| puts elemento } percorre_array(lista) do |elemento| puts elemento.reverse end
Agora sim o código funciona. A linha yield elemento, tem o efeito de enviar para o bloco visitante a variavel elemento como parametro. Assim a cada iteracao do loop for um novo valor é enviado ao código visitante através da variavel elemento. Com isso nosso trabalho está terminado, implementamos as funcionalidades solicitadas, sem repetir código, e de quebra aprendemos sobre DRY e blocos de código em Ruby.
Bloco de código é um assunto bem interessante, e aqui nós abordamos só uma pequena parte. Tem ainda um lance de lambdas, enfim como disse antes o assunto é muito interessante, e se você quiser saber mais pode olhar um post do Anderson Leite sobre isso, o link é este.
Abraços.