Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante

Postado em
12 December 2007 @ 1:38

Tag(s)
Artigos, Ruby

Tradução de artigo – Aquecimento Ruby, parte 2: Métodos ausentes, móveis e manufaturados

Finalizando a série, Russ Olsen mostra técnicas mais exóticas para definição de métodos em Ruby.

Russ, obrigado novamente por autorizar a tradução de seus artigos. Boa sorte com o livro.
(Russ, thank you again for authorizing the translation of your articles. Good luck with the book.)

Aquecimento Ruby 2: Métodos ausentes, móveis e manufaturados

No último artigo, falamos sobre como a leitura de código real é uma ótima maneira de aprender uma nova linguagem de programação. Isto se aplica a qualquer linguagem, incluindo Ruby, mas Ruby apresenta alguns desafios ao aprendiz porque há muitas maneiras em Ruby para conectar código a um nome de método.

Vimos algumas formas mais comuns de definir métodos em objetos Ruby: você pode fazer o mais tradicional e colocar o método na classe, superclasse ou classes superiores do objeto. Você pode também definir o método em um módulo que seja incluído (“mixed in” como nós dizemos) na classe do objeto (ou em qualquer superclasse). Por último, vimos que você pode definir um método singleton em um objeto individual, um método que existe apenas naquele objeto.

Desta vez vamos ver três meios mais exóticos de ligar my_object.this_method com algum código.

Method_missing: O método de mil faces

Imagine que você está tentando rastrear um código que chama my_car.drive_to_washington, mas não encontra o método drive_to_washington em lugar algum. Você procurou em suas classes e módulos. Você ficou de olho nos métodos singleton. Você procurou e procurou, usou o grep várias vezes e aquele método drive_to_washington simplesmente não está em lugar nenhum. Relaxe, você pode ter encontrado um caso de method_missing (música assustadora).

Nós vimos que quando um método é chamado em um objeto, Ruby faz uma busca exaustiva – procura na classe singleton do objeto e em seus módulos, então na classe regular e seus módulos e então na superclasse e assim em diante. Mas e se o método não é encontrado? Você vai receber uma exception, certo?

Não exatamente: quando Ruby não encontra um método na primeira busca, ela simplesmente não desiste. Ao invés disso, Ruby começa a caça ao método novamente, iniciando com a classe singleton e subindo a cadeia, desta vez procurando por um método chamado method_missing. Essencialmente, Ruby tenta informar seu objeto de que alguém chamou um método nele e que esse método estava ausente. Ruby faz isso chamando o método method_missing. O que acontece depois realmente depende de você. Não faça nada e method_missing da classe Object eventualmente lançará a exception que você espera. Mas você pode também definir seu próprio método method_missing para capturar essas chamadas inesperadas e fazer o que você quiser.

Para ver como isso funciona na prática, vamos definir method_missing para implementar aquela misteriosa chamada a my_car.drive_to_washington:

class Car
  def method_missing(method_name, *arguments)
    if /^drive_to_/ =~ method_name.to_s
      place = method_name.to_s.sub(/^drive_to_/, '')
      puts "I'm driving to #{place}"
    else
      super
    end
  end
end

Na verdade, não há muito acontecendo aqui. O método method_missing é chamado com o nome do método ausente e também todos os argumentos passados na chamada. O que a classe Car faz é olhar para o nome do método: se o nome do método começa com a string “drive_to_” então o código recorta a parte final do nome do método e anuncia que é para onde estamos indo. Note que se o nome do método não se encaixar nessa condição, method_missing chama super, que eventualmente chamar method_missing de Object e teremos uma exception.

Com a nossa nova versão da classe Car, podemos dirigir para onde quisermos:

c = Car.new
c.drive_to_washington
c.drive_to_ny

O ponto chave a notar sobre o modo que implementamos os métodos drive_to_washington e drive_to_ny é que as strings “drive_to_washington” e “drive_to_ny” nunca realmente aparecem no código-fonte. Isto é algo que você deve manter em mente enquanto está procurando por algum método enigmático: ele pode estar escondido, nascente, em method_missing.

Métodos aqui e métodos lá

Algumas vezes a explicação para esses métodos difíceis de encontrar é muito mais simples que method_missing. Por exemplo, você pode estar procurando por um método e pode ter procurado na definição da classe, nos módulos e em todos métodos method_missing e ainda assim não encontrar nada. O que acontece?

Espere… você disse definição da classe? Veja bem, em Ruby não existe algo chamado definição de classe. Muitas classes são definidas em um só lugar, como abaixo:

class Convertible < Car
  def color
    "Sizzling red"
  end
 
  def top_down
    puts "Putting the top down"
  end
 
  def top_up
    puts "Putting the top up"
  end
end

Mas também é perfeitamente possível definir uma classe em várias partes. Você pode, por exemplo, encontrar a primeira parte de seu conversível em um arquivo:

class Convertible < Car
  def color
    "Sizzling red"
  end
end

E então encontrar mais dela em um segundo arquivo:

class Convertible
  def top_down
    puts "Putting the top down"
  end
 
  def top_up
    puts "Putting the top up"
  end
end

Note que a segunda parte não cria uma nova classe Convertible; ao invés disso, ela reabre a classe já existente e adiciona alguns métodos a ela.

O que é importante manter em mente sobre Ruby é que, quando você define uma classe, você não está fazendo algo tão especial. O que você está fazendo é criar um novo objeto – uma instância de Class. O objeto que você cria, Convertible em nosso exemplo, é especial de algumas maneiras (você pode criar instâncias dele, por exemplo) mas no final é apenas um objeto. Em Ruby você pode modificar classes existentes tão facilmente quanto você cria novas. Então se é conveniente definir uma parte de uma classe aqui e outra lá, as pessoas farão assim. Você apenas deve manter os olhos abertos para isso.

Manufaturando métodos com Metaprogramação

O último tópico sobre a criação de métodos que vamos estudar é a metaprogramação. Metaprogramação é uma dessas técnicas muito poderosas que podem tornar a escrita de código muito mais fácil mas que também pode tornar o código completamente incompreensível se você não entender a técnica. Mas continue lendo, porque você a entenderá…

A idéia da metaprogramação é de que seu programa se modifique a medida que roda. Isto é relevante para nós porque um meio muito popular de modificar classes via metaprogramação é adicionar um método a ela. Um meio de fazer isso é simplesmente avaliar o código para adicionar o método:

class Car
  def self.add_destination(dest)
    code = %Q{
      def drive_to_#{dest}
        puts "Good bye!"
        puts "I'm driving to #{dest}"
      end
    }
    eval(code)
  end
end

O exemplo acima define um método de classe chamado add_destination. A primeira coisa que o método add_destination faz é montar uma string (a sintaxe %Q{…} é apenas um meio conveniente de definir uma string em várias linhas em Ruby) contendo código Ruby para o novo método. Então ele avalia (eval) essa string como código Ruby. Método instantâneo.

Então se você chamar Car.add_destination(‘washington’), a string acaba sendo:

      def drive_to_washington
        puts "Good bye!"
        puts "I'm driving to washington"
      end

Avalie isto e você terminará com um novo método chamado drive_to_washington na classe Car.

Você também pode definir métodos um pouco mais diretamente utilizando o método (qual outro?) define_method:

  class Car
    def self.add_destination(dest)
      define_method "drive_to_#{dest}" do
        puts "Good bye!"
        puts "I'm driving to #{dest}"
      end
    end
  end

Há algumas diferenças práticas entre a técnica do eval e do define_method, principalmente no que diz respeito à visibilidade das variáveis, mas em um nível bem grosseiro são a mesma coisa: em ambos os casos você termina com um novo método sem uma definição explícita do mesmo no código fonte.

Finalizando

Então é isso, vários meios de conectar aquela chamada de método a algum código Ruby. Na parte 1 nós vimos que você pode fazer o mais tradicional adicionando métodos na classe ou nas superclasses ou pode definir métodos em um módulo e incluí-lo em sua classe. Não apenas isso, mas você pode definir métodos em objetos individuais. Desta vez, vimos como você pode capturar chamadas a métodos não existentes com method_missing ou espalhar suas definições de métodos em arquivos diferentes. Finalmente, demos uma rápida olhada na metraprogramação, um modo de criar métodos novos dinâmicamente.

Se você é novo no Ruby você pode estar pensando se a única possível razão pela qual Ruby suporta toda essa variedade é para deixar os iniciantes malucos. Eu sei que foi assim que me senti no início. Mas cada uma das técnicas mais exóticas de definição de métodos em Ruby tem sua razão; Cada uma alivia a carga de construir código a sua maneira. Quer saber como? Creio que a melhor maneira de aprender isso é pegar seu programa Ruby favorito e comear a ler o código.

Russ Olsen.


1 Comentário

Comentário por
Marcelo Madeira
19 February 2008 @ 16:48

Excelente.
Estou estudando ruby e a cada dia me surpreendo mais.


Deixe um comentário