Exemplos de metaprog (Java e Ruby)

Quem desenvolve com Ruby pode até estar acostumado a fazer coisas como utilizar os attr_accessor, attr_writer, etc. que são bons exemplos de metaprog. Mas como é que isso funciona? Vamos ver alguns exemplos em Java e Ruby e mostrar quais são as diferenças entre essas duas linguagens.

class Attrs
  attr_accessor :accessor
  attr_reader :reader
  attr_writer :writer
  attr :attr
end

Esse exemplo mostra os quatro métodos que criam variáveis de instância e métodos para acessar essas variáveis. Lembrando que eles são métodos, ou seja, aquelas linhas de código estão na verdade chamando um método. Esse método, definido dentro da classe Module do Ruby [1], cria uma variável de instância com o nome que você passa como um símbolo, por isso precisamos passar algo da forma “:accessor”, e alguns métodos para oferecer acesso a essa variável de instância utilizando a metaprogramação.

Considerando a classe acima podemos chamar, dentro do escopo dela, qualquer um dos atributos como “@accessor”. Para instâncias dessa classe podemos chamar os métodos “accessor” que retorna o valor daquela variável e “accessor=” que altera o valor da variável.

Podemos então utilizar o seguinte método, que, através de reflexão, recebe um objeto e um atributo, e mostra a acessibilidade do atributo no método.

def get_accessibility(object, attribute)
  puts "Can read? #{object.respond_to?("#{attribute}".to_sym)}"
  puts "Can write? #{object.respond_to?("#{attribute}=".to_sym)}"
end

Esse método utiliza os método “respond_to?” que pode ser chamado em um objeto qualquer, toma como parametro um símbolo e retorna se o objeto tem ou não aquele método. No exemplo estamos construindo o símbolo utilizando o nome do atributo, para o método de leitura apenas precisamos chamar o nome do atributo, para o método de escrita precisamos chamar com um “=” na frente. O resultado da chamada do método nos atributos de um objeto da classe acima mostra a visibilidade dos atributos.

=== Accessor ===
Can read? true
Can write? true
=== Reader ===
Can read? true
Can write? false
=== Writter ===
Can read? false
Can write? true
=== Attribute ===
Can read? true
Can write? false

Vale lembrar que esses métodos são bem diferentes dos modificadores de acesso do Java, pois os modificadores apenas configuram o objeto para não permitir acesso as variáveis. Os métodos attrs do Ruby definem métodos e a variável de instância, e nada lhe impede de fazer qualquer modificação ou escrever métodos de leitura/escrita. No Java também não é tão complicado modificar a acessibilidade dos atributos. Os exemplos a seguir mostram como modificar o acesso de variáveis com Java e Ruby.

Considere a classe java a seguir, com um método privado e uma variável privada.

public class HelloYou {
  private String name;

  public HelloYou(String name){
    this.name = name;
  }

  private String sayHello() {
    return "Hello " + name + "!";
  }
}

O código a seguir mostra como acessar o método privado, e como ler e escrever na classe privada, utilizando a api de reflection do java. Os objetos Method e Field permitem acessar métodos e atributos de uma classe.

import java.util.Scanner;
import java.lang.reflect.*;

public class Main{
  public static void main(String arg[]){
    Scanner scanner = new Scanner(System.in);
    System.out.print("Quem é você? ");
    String name = scanner.nextLine();
    Method privateMethod;
    Field privateField;
    HelloYou hello = new HelloYou(name);
    try {
      privateMethod = hello.getClass().getDeclaredMethod("sayHello");
      privateMethod.setAccessible(true);
      System.out.println(privateMethod.invoke(hello));
      privateField = hello.getClass().getDeclaredField("name");
      privateField.setAccessible(true);
      privateField.set(hello, ((String)privateField.get(hello)).toUpperCase());
      System.out.println(privateMethod.invoke(hello));
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}

A ideia é pegar um objeto java.lang.reflect.Method e atribuir a ele um dos métodos do objeto que você quer acessar, utilizan o “getClass()” para acessar a classe Class do seu objeto. Uma vez que temos a classe de um objeto, podemos acessar os métodos através do nome do método e os tipos dos parâmetros, bem como os atributos através do nome.

Chamando o método “setAccessible()” nós podemos modificar a visibilidade dos métodos e atributos. Em seguida basta chamar “invoke()” para chamar o método no objeto passado como parâmetro. Para os atributos apenas chamamos “set()” e “get()” de um objeto java.lang.reflect.Field para manipular os valores dos atributos no objeto.

No java, as possibilidades de metaprogramação são bem reduzidas pois não permitem adicionar métodos na classe ou alterar os métodos em tempo de execução. Mas essa é a filosofia da linguagem, reduzir a possibilidade de erros dos programadores através da remoção de poder da linguagem. Mas mesmo assim, alguém com conhecimento na linguagem pode ignorar os modificadores de visibilidade caso seja necessário.

O mesmo exemplo acima em poderia ser algo assim se fosse escrito em Ruby:

class HelloYou
  attr_reader :name

  def initialize(name)
    @name = name
  end

  private
  def say_hello
    "Hello #{@name}"
  end
end

puts "Quem é você?"
name = gets.chomp

hello = HelloYou.new(name)
puts hello.send(:say_hello)
def hello.name=(name)
  @name = name
end
hello.name = name.upcase
puts hello.send(:say_hello)

Para acessar métodos privados, a única coisa que precisamos é chamar o método “send()” e passar o símbolo que representa o método. Já para permitir escrever em um atributo sem um método de escrita, basta definir o método que dá acesso a ele. No exemplo acima, o método foi definido diretamente no objeto, e não na classe.

Ou seja, com um pouco de conhecimento, podemos simplesmente ignorar as regras de visibilidade e acesso de atributos e métodos. Definir um método como privado então serve apenas como documentação do código, ou seja, apenas diz que aquele método não depende de nada fora do objeto e, da mesma forma, não altera nada que esteja fora desse objeto.

Em Ruby a metaprogramação é ainda mais poderosa pois podemos adicionar, modificar, remover, executar, etc. qualquer código em tempo de execução. No entanto esse “poder” exige cuidado para evitar complicações de manutenção de código. Um dos pontos negativos da forte utilização de metaprogramação em Ruby é que, quando se abusa dela, fica muito difícil debugar e manter o código, pois você pode ver no stack trace que ocorreu um erro em um determinado método e, ao procurar por esse método, não encontrar nada.

Na verdade, apesar de ser interessante, dificilmente você vai precisar alterar a visibilidade de métodos e atributos. Geralmente faz mais sentido repensar no design e alterar a visibilidade pelos meios convencionais do que oferecer isso em algum método obscuro. Uma das utilidades é para testar métodos privados, sem entrar na questão de testar ou não métodos privados.

Nos próximos posts vou procurar mais alguns exemplos legais e que fazem mais sentido do que só testar métodos privados. Para conhecer um pouco mais sobre metaprog e reflection em Ruby, vale a pena dar uma olhada no post sobre Reflexão em Ruby [2] e Metaprogramação em Ruby [3].

Referências:

[1] http://www.ruby-doc.org/core-1.9.3/Module.html#method-i-attr
[2] https://brizeno.wordpress.com/2012/03/25/reflexao-em-ruby/
[3] https://brizeno.wordpress.com/2012/03/27/metaprogramacao-em-ruby/

Anúncios

2 comentários sobre “Exemplos de metaprog (Java e Ruby)

  1. Uma dúvida que tenho, tenho um projeto em Java (por enquanto) e gostaria de chamar uma aplicação em Ruby, mas gostaria de chamar a classe e obter um retorno da mesma, seria possível?

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s