O padrão de projeto Visitor e Vtables em C++

Agosto 26, 2012

Outro dia estava desenvolvendo um código Java em que precisei usar instanceof, mas como já havia lido que o uso dessa palavra-chave é sinal de design ruim, busquei ajuda e me recomendaram o padrão de projeto Visitor.

Neste post vamos falar sobre esse padrão de projeto em Java, que foi onde minha dúvida se originou e também vamos discuti-lo em C++, porque sua implementação tem relação com a estrutura Vtables que eu queria estudar a algum tempo.

Visitor em Java

Consideremos o seguinte exemplo, onde temos as classes Cachorro e Gato que são especializações de Animal e que possuem métodos para emissão de som, respectivamente latir() e miar(), respectivamente.

Queremos implementar um método emitirSom(), que recebe uma instância de Animal e emite o som de acordo com a classe instanciada. Uma alternativa usando instanceof é a seguinte:

// Listagem 1
interface Animal {    
}
public class Cachorro implements Animal {
	public void latir(){
		System.out.println("Au!");
	}
}
public class Gato implements Animal {
	public void miar(){
		System.out.println("Miau");
	}
}
...
public static void emitirSom(Animal animal){
    if(animal instanceof Cachorro){
        Cachorro cachorro = (Cachorro) animal;
        cachorro.latir();
    }
    else if(animal instanceof Gato){
        Gato gato = (Gato) animal;
        gato.miar();
    }
}

Aqui cabe uma citação a Scott Meyers, no seu livro Effective C++:

Anytime you find yourself writing code of the form “if the object is of type T1, then do something, but if it’s of type T2, then do something else,” slap yourself.

Para nos livrarmos do instanceof, basta adicionarmos um método emitirSom() à interface Animal e substituir latir()/miar(). No método emitirSom() usamos polimorfismo para escolher a implementação correta.

// Listagem 2
interface Animal {
    void emitirSom();
}
public class Cachorro implements Animal {
    @Override
    public void emitirSom(){
        System.out.println("Au!");
    }
}
public class Gato implements Animal {
    @Override
    public void emitirSom(){
        System.out.println("Miau");
    }
}
...
public static void emitirSom(Animal animal){
    animal.emitirSom();
}

Outra possibilidade é delegar a implementação de emitirSom() para uma outra classe através do padrão de projeto Visitor. Esse padrão considera dois tipos de classes: um elemento e um visitante. O elemento implementa um método geralmente chamado de accept() e o visitante o método visit().

O método accept() recebe um visitante e chama o método visit() desse visitante passando a si mesmo como parâmetro. Desta forma, o visitante pode implementar o método visit() para cada tipo.

// Listagem 3
interface Animal {
    void emitirSom();
    void accept(AnimalVisitor visitor);
}
public class Cachorro implements Animal {
    @Override
	public void accept(AnimalVisitor visitor){
        visitor.visit(this);
    }
}
public class Gato implements Animal {
    @Override
	public void accept(AnimalVisitor visitor){
        visitor.visit(this);
    }
}
interface AnimalVisitor {
    void visit(Cachorro cachorro);
    void visit(Gato gato);
}
public class EmissorDeSom implements AnimalVisitor {
    @Override
	public void visit(Gato gato){
        System.out.println("Miau");
    }
    @Override
	public void visit(Cachorro cachorro){
        System.out.println("Au!");
    }	
}

A vantagem dessa abordagem é que a classe EmissorDeSom pode conter membros e métodos comuns ao modo como os animais emitem som, que de outra forma acabaria sendo implementado na classe Animal.

Outra vantagem é que a implementação do visitante pode ser escolhida em tempo de compilação e, sendo uma injeção de dependência, facilita testes.

Visitor em C++

Em C++ não temos a palavra-chave instanceof, mas podemos usar a função typeid().

Segundo [1], se o argumento dessa função for uma referência ou um ponteiro de-referenciado para uma classe polimórfica, ela retornará um type_info correspondendo ao tipo do objeto em tempo de execução.

// Listagem 4
struct Animal {
    virtual void foo(){};
};
struct Cachorro : public Animal {
    void latir(){
        cout << "Au!\n";
    } 
};
struct Gato : public Animal {
    void miar(){
        cout << "Miau\n";
    }
};
void emitirSom(Animal *animal){
    if(typeid(*animal) == typeid(Cachorro)){
        Cachorro *cachorro = dynamic_cast<Cachorro *>(animal);
        cachorro->latir();
    }
    else if(typeid(*animal) == typeid(Gato)){
        Gato *gato = dynamic_cast<Gato *>(animal);
        gato->miar();
    }
}

Novamente, podemos criar um método emitirSom() em uma interface e usar o polimorfismo. Em C++ não temos interface, mas podemos definir um método puramente virtual como no código abaixo.

// Listagem 5
struct Animal {
    virtual void emitirSom() = 0;
};
struct Cachorro : public Animal {
    void emitirSom(){
        cout << "Au!\n";
    }
};
struct Gato : public Animal {
    void emitirSom(){
        cout << "Miau\n";
    }
};
void emitirSom(Animal *animal){
    animal->emitirSom();
}

A implementação do padrão Visitor pode ser feita da seguinte maneira:

// Listagem 6
struct Gato;
struct Cachorro;

struct AnimalVisitor {
    virtual void visit(Gato *gato) = 0;
    virtual void visit(Cachorro *cachorro) = 0;
};
struct Animal {
    virtual void accept(AnimalVisitor *visitor) = 0;
};
struct EmissorDeSom : public AnimalVisitor {
    void visit(Gato *gato){
        cout << "Miau\n";
    }
    void visit(Cachorro *cachorro){
        cout << "Au!\n";
    }    
};
struct Cachorro : public Animal {
    void accept(AnimalVisitor *visitor){
        visitor->visit(this);
    }
};
struct Gato : public Animal {
    void accept(AnimalVisitor *visitor){
        visitor->visit(this);
    }
};
void emitirSom(Animal *animal){
    AnimalVisitor *visitor = new EmissorDeSom();
    animal->accept(visitor);
}

Despacho único e despacho múltiplo

Vamos agora analisar como C++ implementa funções virtuais. Antes disso, precisamos entender o conceito de despacho dinâmico.

Despacho dinâmico é uma técnica utilizada no caso em que diferentes classes implementam diferentes métodos, mas não se sabe qual o tipo do objeto que chama o método em tempo de compilação, como nos exemplos acima.

Em C++ e Java, esse tipo de despacho é também chamado de despacho único (single dispatch) porque ele só leva em conta o tipo do objeto que chama o método. Em Lisp e algumas outras poucas linguagens, há o chamado despacho múltiplo que também leva em conta o tipo de parâmetro.

Por exemplo, considere o código abaixo:

// Listagem 7
void visitor(Cachorro *cachorro){
    cout << "Au!\n";
}
void visitor(Gato *gato){
    cout << "Miau!\n";
}
void emitirSom(Animal *animal){
    visitor(animal);
}

Teremos um erro de compilação, porque não temos ligação dinâmica a partir dos parâmetros.

Agora veremos como o despacho dinâmico é implementado em C++.

C++ Vtables

Embora a especificação do C++ não diga qual a maneira de implementar o despacho dinâmico, a maioria dos compiladores fazem uso de uma estrutura chamada virtual method table, também conhecida por outros nomes como vtable. Essencialmente, essa tabela é um array de ponteiros para os métodos virtuais.

Cada classe que define ou herda pelo menos um método virtual possui sua vtable. Ela aponta para os métodos virtuais definidos pelo seu ancestral (incluindo a si mesmo) mais próximo que podem ser utilizados por aquela classe. Além disso, cada instância de uma classe com vtable possui um ponteiro para essa tabela. Esse ponteiro é setado quando instanciamos esse objeto.

Considere o trecho da Listagem 5:

Animal *animal = new Cachorro();

Aqui o ponteiro de animal aponta para a vtable de Cachorro, e não de Animal. Então, quando fazemos

animal->emitirSom();

é feita uma consulta para saber exatamente qual implementação emitirSom() chamar. Note que para métodos virtuais existe uma indireção que pode afetar o desempenho. Por isso, por padrão, o despacho dinâmico não é efetuado em C++, a menos que alguma classe adicione a palavra-chave virtual. Em Java o despacho é habilitado por padrão.

Vamos a um exemplo,

struct A {
    virtual void foo(){}
    virtual void bar(){}
    void baz(){}
};
struct B : public A {
    void bar(){}
    virtual void baz(){}
};

Podemos analisar as vtables das classes A e B compilando o código acima com gcc usando a opção -fdump-class-hierarchy. Um arquivo de texto com a extensão .class será gerado.

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1A)
8     (int (*)(...))A::foo
12    (int (*)(...))A::bar

Class A
   size=4 align=4
   base size=4 base align=4
A (0xb71f6038) 0 nearly-empty
    vptr=((& A::_ZTV1A) + 8u)

Vtable for B
B::_ZTV1B: 5u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1B)
8     (int (*)(...))A::foo
12    (int (*)(...))B::bar
16    (int (*)(...))B::baz

Class B
   size=4 align=4
   base size=4 base align=4
B (0xb7141618) 0 nearly-empty
    vptr=((& B::_ZTV1B) + 8u)
  A (0xb71f61f8) 0 nearly-empty
      primary-for B (0xb7141618)

Aqui podemos ver que a função A::foo e A::bar aparecem na vtable de A, mas a função A::baz não, porque ela não foi declarada virtual. Na vtable de B temos A::foo, porque ela não foi sobrescrita em B. Temos também B::bar, embora ela não tenha sido declarada virtual em B, essa propriedade foi herdada de A::bar. Finalmente, B::baz aparece na vtable porque foi declarada como virtual em B.

Podemos ver também o valor que os ponteiros que as instâncias dessas classes terão: vptr=((& A::_ZTV1A) + 8u) e vptr=((& B::_ZTV1B) + 8u), que é respectivamente onde os ponteiros para as funções começam nas respectivas tabelas.

Uma referência interessante para compreender melhor vtables pode ser vista em [2].

Referências

[1] The typeid operator (C++ only)
[2] LearnCpp.com – The Virtual Table


Introdução a Jbehave

Julho 22, 2012

Neste post vamos falar um pouco sobre desenvolvimento orientado a comportamento (Behaviour Driven Development) também conhecido como BDD e focar em uma das ferramentas que facilitam a execução dessa prática, o jbehave.

As ferramentas utilizadas são Maven 3, jbehave (v 3.6.8) e o jUnit (4.10). O código-fonte do projeto está disponível no github.

Introdução

O desenvolvimento orientado a comportamento é uma evolução do desenvolvimento orientado a testes (Test Driven Development) ou TDD. Falamos um pouco sobre TDD em um post anterior.

Uma das motivações para a reformulação da prática foi o fato de que muitos adeptos não percebiam que TDD é mais sobre definição de comportamento do que sobre testes propriamente dito.

O BDD visa minimizar falhas de comunicação entre o pessoal de negócio e o pessoal técnico. Para isso, se baseia em um vocabulário comum e conciso para garantir que todos usem os mesmos termos, utilizando em geral o projeto orientado a domínio (Domain Driven Design).

Os testes definem comportamentos, ou seja, a relação entre os domínios do sistema. A ideia é que com o uso de um vocabulário comum, os testes sirvam como documentação das especificações do sistema, de modo que possam ser lidos pelos desenvolvedores, testadores e analistas de negócio.

Algumas ferramentas auxiliam nesse processo, permitindo a descrição dos testes em linguagem natural. Algumas dessas ferramentas para trabalhar com BDD em Java são o jbehave, o Concordion e o easyb (que na verdade usa Groovy). Uma outra bastante conhecida é o RSpec, voltada para Ruby.

Jbehave

O jbehave é um framework de desenvolvimento orientado a comportamento ou BDD (Behaviour Driven Development), desenvolvido por Dan North.

http://brianrepko.files.wordpress.com/2010/09/jbehave-logo.png

Exemplo prático

O uso do jbehave consiste em 3 passos principais:

  1. A implementação de uma classe POJO com anotações para criar uma correspondência entre o texto da estória e os métodos dessa classe.
  2. A criação de uma estória (essencialmente um arquivo de texto com extensão .story)
  3. A implementação de um embutidor ou embarcador (traduções livres de embedder) para que as estórias sejam executadas através de um framework de testes, que no nosso caso será o JUnit.

Vamos considerar um exemplo bastante simples: uma classe com um método que soma dois inteiros e guarda o resultado em uma variável interna. Vamos chamá-la de Adder:

public class Adder {

    private int result;
	
    public void add(int a, int b){
        result = a + b;
    }	
    public int getResult(){
        return result;
    }	
}

Implementação do POJO

Vamos criar uma classe que fará a correspondência entre a estória e os métodos de teste. Vamos chamá-la de AdderSteps.

public class AdderSteps {
	
    private Adder adder;
	
    @Given("um somador é criado")
    @Aliases(values={"um somador é instanciado"})
    public void theAdderIsCreated(){
        adder = new Adder();
    }
	
    @When("eu adiciono $a e $b")
    public void iAddTwoNumbers(int a, int b){
        adder.add(a, b);
    }

    @Then("o resultado deve ser $c")
    public void theResultMustBe(int c){
        assertEquals(adder.getResult(), c);
    }

}

O método theAdderIsCreated instancia um Adder, iAddTwoNumbers faz a soma dos dois números através do método do Adder e theResultMustBe verifica se o resultado da soma é igual ao parâmetro (usa o JUnit).

O principal destaque do código acima são as anotações Given, Aliases, When e Then.

A anotação Given faz a associação com a linha começando com a palavra-chave given no arquivo de texto e continuando com o texto passado como parâmetro. Assim, se houver a linha no arquivo de texto da estória

Given um somador é criado

O método theAdderIsCreated() será chamado. O uso da anotação Aliases permite que o método seja chamado com outros nomes. No caso a linha abaixo

Given um somador é instanciado

também chamaria o método.

As anotações When e Then são análogas para as palavras-chaves when e then. Porém, no exemplo vemos que as strings de parâmetro dessas anotações têm o símbolo $.

Isso faz a associação das variáveis com os valores presentes no arquivo de estória. Por exemplo, a linha

When eu adiciono 1 e 2

associará a variável a com 1 e a variável b com 2, chamando o método iAddTwoNumbers(1, 2).

Definição de uma estória

Com a classe que faz as correspondências implementada, podemos criar um caso de teste através de uma estória:

Given um somador é criado
When eu adiciono 1 e 4
Then o resultado deve ser 5

Veja como o teste fica bem mais legível nessa forma do em código, servindo como uma documentação natural.

Se as especificações são dadas em português então faz sentido o texto também ser escrito assim. Entretanto, o uso de palavras-chaves em inglês deixa o texto estranho. A seguir veremos como contornar este problema.

Implementação do embutidor para o JUnit

Agora podemos embutir nossa estória em um framework de testes, no caso o JUnit. O jbehave define uma classe própria para isso chamada JUnitStory. Agora basta estendê-la e sobrescrever o método stepsFactory(), para instanciar a nossa classe de associação, o AdderSteps.

public class TestAdder extends JUnitStory {
 
    @Override
    public InjectableStepsFactory stepsFactory() {        
        return new InstanceStepsFactory(configuration(), new AdderSteps());
    }
}

O método configuration() retorna um objeto Configuration contendo diversas configurações. Para personalizá-las, podemos sobrescrever o método configuration():

@Override
public Configuration configuration() {
    return new MostUsefulConfiguration()
        // Onde procurar pelas estórias
        .useStoryLoader(new LoadFromClasspath(this.getClass()))
        // Para onde fazer os reports
        .useStoryReporterBuilder(new StoryReporterBuilder()
                                 .withDefaultFormats()
                                 .withFormats(Format.CONSOLE, Format.HTML)); 
}

A classe Configuration usa o encadeamento de método para obter uma interface mais fluente.

O método MostUsefulConfiguration() instancia uma configuração padrão e o useStoryLoader() personaliza o modo como as estórias são carregadas. Neste caso, estamos especificando que ele fica no mesmo caminho da classe.

A classe está no pacote me.kuniga.jbehave e por convenção fica em test/java/me/kuniga/jbehave. Portanto, podemos colocar as estórias em test/resources/me/kuniga/jbehave que o maven tratará de colocá-las no mesmo caminho.

O método useStoryReporterBuilder() configura como os resultados do teste serão exibidos. No código acima estamos indicando que tais resultados serão exibidos na saída padrão e exportados para html, que por padrão ficam em target/jbehave/

A classe JUnitStory possui um método anotado com @Test e por isso nossa classe TestAdder será executada pelo JUnit.

Nota: Até o momento não consegui rodar os testes através do maven devido a problemas de encoding (a associação com os métodos não é feita corretamente). Se eu descobrir o porquê disso edito o post. Por ora, é possível rodar os testes pelo próprio Eclipse.

Ao executarmos os testes, um relatório estará disponível em target/jbehave/me.kuniga.jbehave.test_adder.html.

Palavras-chaves em Português

É possível configurar o JBehave para reconheça as palavras-chaves traduzidas. Primeiramente devemos instanciar um objeto da classe Keywords com o idioma português. Felizmente o JBehave já provê um arquivo de propriedades com as traduções. Portanto, basta fazermos:

Keywords keywords = new LocalizedKeywords(new Locale("pt"));

As traduções para as palavras-chaves são: “Given” – “Dado que“; “When” – “Quando“; “Then” – “Então“.

Então configuramos as palavras-chave e o parser das estórias através dos métodos useKeywords() e useStoryParser() [2].

O método configuration() completo fica:

public Configuration configuration() {
    	
    Keywords keywords = new LocalizedKeywords(new Locale("pt"));
    	
    return new MostUsefulConfiguration()
        .useKeywords(keywords)
        .useStoryParser(new RegexStoryParser(keywords))
        // Onde procurar pelas estórias
        .useStoryLoader(new LoadFromClasspath(this.getClass()))
        // Para onde fazer os reports
        .useStoryReporterBuilder(new StoryReporterBuilder()
                                 .withDefaultFormats()
                                 .withFormats(Format.CONSOLE, Format.HTML)); 
}

Agora podemos re-escrever nossa estória como:

Dado que um somador é instanciado
Quando eu adiciono 1 e 4
Então o resultado deve ser 5

Código-fonte do JBehave

O código-fonte do jBehave vem com alguns exemplos. Pode-se obter o código usando git:

git clone git://git.codehaus.org/jbehave-core.git

Os exemplos se encontram em jbehave-core/examples/.

O arquivo de propriedades com a tradução das palavras-chaves além de outros termos pode ser encontrado em:

jbehave-core/jbehave-core/src/main/resources/i18n/keywords_pt.properties

Conclusão

Achei muito bacana a ideia de gerar o código a partir de uma descrição textual, principalmente em casos em que gerar um caso de teste exige muita configuração.

Além do mais, o teste fica naturalmente documentado e essa documentação fica atrelada ao código, não correndo o risco de ficar ultrapassada (princípio DRY).

Para criar instâncias complexas é interessante o uso do encadeamento de métodos sobre o qual falamos acima. Cada método de configuração poderia ser associado a um @Given. Combinando com as ideias de [3], poderíamos especificar um linguagem de domínio específico (Domain Specific Language ou DSL) para testes usando o jbehave!

Referências

[1] Behaviour-driven.org – Introduction
[2] jbehave – Stories in your language
[3] The Java Fluent API Designer Crash Course


Review – The Pragmatic Programmer: From Journeyman to Master

Julho 1, 2012

Faz algum tempo que comprei o livro The Pragmatic Programmer: From Journeyman to Master (há também a versão em português, dica do Leonardo) e recentemente terminei de lê-lo.

É um livro sobre desenvolvimento de software. Encontrei boas recomendações para ele em diversos sites e portanto decidi dar uma olhada. O livro está organizado em 8 capítulos que estão divididos em seções menores, num total de 46.

Cada uma dessas seções corresponde a uma dica relacionada a alguma das diversas fases do processo de desenvolvimento de sofware, incluindo levantamento de requisistos, projeto, desenvolvimento e teste. Vou comentar brevemente sobre os tópicos que achei mais interessantes.

A Teoria da janela quebrada. Uma janela quebrada em um prédio de uma cidade dá a impressão de abandono aos habitantes locais. Aí começam a aparecer pichações, danos estruturais começam a aparecer até que o senso de abando se torna evidente. Analogamente, uma janela quebrada no desenvolvimento de software são designs ou códigos ruins, que se forem deixados para consertar depois dão a sensação de descaso com a qualidade do software e fatalmente a qualidade do código se deteriorará.

Software bom o bastante. A dica pode ser resumida na frase “um ótimo software hoje é melhor do que um software perfeito amanhã”.

Seu portifólio de conhecimento. Os autores sugerem metas para construir e atualizar sua base de conhecimento:

  • aprenda uma linguagem todo ano
  • leia um livro técnico a cada trimestre
  • leia livros não-técnicos
  • assista aulas

Princípio DRY. Em inglês, DRY é um acrônimo Don’t Repeat Yourself. A motivação é que duplicação causa re-trabalho e devemos sempre manter as duplicações consistentes. Pra mim essa dica parecia óbvia, mas eu nunca tinha pensado nela conscientemente.

Um caso em que ela aparece é nos comentários de código. Um comentário que explica o código viola o princípio DRY porque o código em si já possui essa informação. De fato, já me deparei diversas vezes com código legado em que o comentário dizia uma coisa e o código fazia outra, provavelmente porque alguém alterou o código mas esqueceu de atualizar o comentário. Isso me lembra que uma citação:

Code never lies, comments sometimes do.

Ron Jeffries

O autor prega que código com bons nomes de variáveis e funções não exige comentário. Eu gosto de comentar pelo menos funções. Nesses casos, se estiver trabalhando com controle de versão, eu geralmente coloco o número da revisão em que em adicionei o comentário, pois se alguém quiser saber se o comentário corresponde ao código, basta fazer um diff. O uso ou não de comentários é um assunto controverso.

Balas traçantes. (Trace bullets) A analogia vem da situação em que você deseja acertar um alvo no escuro usando uma metralhadora. Uma abordagem é calcular precisamente a posição do alvo, a orientação da arma e atirar. Isso funciona bem quando o alvo é estático, mas em muitos casos o alvo é móvel e o tempo gasto calibrando o alvo pode ter sido em vão. Uma abordagem diferente é usar balas que deixam rastros luminosos definindo sua trajetória. Com isso o atirador tem um feedback que o ajuda a calibrar o próximo tiro.

No contexto de desenvolvimento de software, temos especificações que mudam com o tempo. Atirar com balas traçantes consiste em implementar uma arquitetura com o mínimo de funcionalidades, mas fazendo uso de todo o sistema desde a consultas simples a banco de dados até a interface gráfica. A vantagem deste método é que você consegue um feedback mais rápido do cliente, o que pode ajudar a consolidar as especificações.

Projeto por contrato. (Design by contract) O conceito de projeto por contrato foi desenvolvido por Bertrand Meyer para a linguagem de programação Eiffel. Basicamente, o contrato é caracterizado por pré-condições, pós-condições e invariantes em uma função. Pré-condições são hipóteses que a função supõe verdadeiras como por exemplo um parâmetro não nulo. As pós-condições são condições que devem ser satisfeitas após a execução do método. Finalmente, as invariantes são condições que valiam antes da chamada da função e continuam valendo depois, embora elas não necessariamente valessem ao longo da execução da função.

Contratos estão relacionados com testes e corretude de código. Recentemente o Google disponibilizou uma biblioteca chamada Cofoja (Contracts for Java) que permite o uso de contratos através de anotações.

Programação assertiva. Consiste no uso de assertivas para verificar condições que você supõe serem verdadeiras. A dica dada é se você acha que uma condição nunca ocorrerá em um determinado trecho de código, então adicione uma asserção para garantir que não irá de fato.

Eu particularmente gosto de assertivas, porque servem como forma de documentar o código e ajudam na depuração de erros, quando alguma hipótese foi violada.

Em Java as assertions são desabilitadas por padrão, pois o argumento é que elas podem impactar o desempenho do código se posta em produção. Eu acho que mesmo em produção assertivas podem ser usadas e se a verificação de alguma delas é tão complexa que pode interferir no tempo de execução do código, talvez valha a pena deixá-la como um teste.

Para garantir que assertivas estão ligadas em Java, podemos adicionar o seguinte trecho de código em alguma classe que será chamada durante a execução do código:

static {
  boolean assertsEnabled = false;
  // Linha não executada com assert desligado
  assert assertsEnabled = true; 
  if (!assertsEnabled)
    throw new RuntimeException("Asserts desabilitados!");
} 

Lei de Deméter. A lei de Deméter é um guia de desenvolvimento para diminuir o acoplamento entre partes de um software. No caso particular de métodos e funções, a lei diz que um método só deve chamar os métodos pertencendo: ao próprio objeto, a um objeto passado como parâmetro, a objetos instanciados dentro do método e a objetos declarados dentro do método.

O nome vem do projeto Demeter desenvolvido da Universidade Northeastern que é sobre programação adaptativa.

O nome do projeto faz referência à Deusa grega da agricultura, Deméter, pois os criadores do projeto estavam trabalhando em uma simplificação de uma linguagem chamada Zeus, queriam nomear a ferramenta com uma nome relacionado e optaram por Deméter, a irmã de Zeus [1].

Acoplamento temporal. Muitos designs buscam o baixo acoplamento estrutural, entre classes do projeto. Um termo que eu não conhecia, mas achei interessante, é o acoplamento temporal, que representa a dependência entre as ações executadas pelo software. Um baixo acoplamento temporal é desejável pois facilita o uso de programação paralela.

Conclusão

O livro contém diversas dicas e descrevi as que achei mais interessantes. O texto está bem escrito e é de leitura fácil. A divisão em pequenas seções facilita a leitura casual.

Outros tópicos cobertos sobre os quais não comentei são: refatoração, testes, levantamento de requisitos, escrita das especificações, uso de ferramentas de modelagem, etc. Pelo que pude perceber, a essência dos conselhos dados segue a linha da metodologia ágil.

Embora eu não esteja seguindo as metas sugeridas, eis aqui o status delas:

  • Livros técnicos: esse foi o primeiro que li no ano
  • Livros não-técnicos: faz muito tempo que não leio um :/
  • Nova linguagem: estou aprendendo Haskell desde o ano passado, mas ainda tenho vontade de aprender R e Erlang
  • Aulas: este ano fiz o curso de PGM! Pretendo me matricular no curso de Visão Computacional até o fim do ano.

Referências

[1] Demeter project: What is Demeter?


Testes Unitários em Java

Janeiro 8, 2012

Hoje vou discutir um pouco sobre testes unitários em Java, especificamente usando os frameworks JUnit e EasyMock.

Introdução

Basicamente, testes unitários visam garantir que as partes de um programa estão corretas, da maneira mais independente possível. Isso em geral é feito através de assertivas, que verificam se as saídas desses componentes respeitam condições pré-estabelecidas.

Testes unitários são bastante utilizados em uma metodologia de desenvolvimento chamada Test Driven Development (TDD). A ideia básica dessa metodologia é, antes de implementar uma funcionalidade, escrever testes que a verifiquem e então implementar o código mais simples que passe em todos os testes. Os testes criados são geralmente testes unitários automatizados e que devem ser executados a cada nova funcionalidade implementada.

Em Java, há um framework chamado JUnit, que auxilia na criação e execução/automação desse tipo de teste. Já o EasyMock, é um complemento de frameworks como o JUnit e pode ser utilizado para diminuir a dependência entre diferentes partes do código.

JUnit

Usar o JUnit em sua forma mais básica é bem simples. A maior parte da configuração é feita através de anotações. Métodos anotados com @Test são invocados pela classe org.junit.runner.JUnitCore.

A principal vantagem do JUnit é sua integração com diversas ferramentas, como por exemplo o eclipse e o maven. O eclipse provê interfaces para a criação de novas classes de testes e a execução dos testes usando o JUnit. Para mais informações sobre a integração com o eclipse, este tutorial [1] é excelente.

O maven, por sua parte, usa na sua convenção de diretórios o src/test, onde devem ser colocados os fontes de testes. Além disso, à fase test está associado o plugin surefire, que roda os testes através do JUnit.

Note que como a fase test é anterior à fase package ou install, na maior parte das vezes em que se faz o build do código, os métodos anotados com @Test são automaticamente executados.

Outra observação é que por padrão, o plugin só considera as classes com os nomes dentro de um desses padrões: **/*Test.java, **/Test*.java ou **/*TestCase.java. Eu particularmente não entendo porque o padrão não inclui também todos os testes do diretório src/test/java/. Enfim, é possível incluir ou excluir classes explicitamente, conforme descrito aqui.

A dependência do JUnit (no meu caso, a versão 4.10) deve ser incluída no arquivo POM:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.10</version>
  <scope>test</scope>
</dependency>

Para pular os testes, principalmente se estes forem muito demorados, pode-se passar o parâmetro
-Dmaven.test.skip=true para o maven.

EasyMock

Dado o princípio de testes unitários de só testar um dado método ou uma função, é importante que isolemos esse pedaço de código de dependências externas. Para isso, podemos usar mocks, que visam reproduzir o comportamento da classe externa.

O framework EasyMock fornece uma maneira simples de criar esses mocks. Dada uma interface java, podemos criar um mock que “implementa” essa interface. Podemos então especificar, para cada método, qual o valor esperado para uma dada entrada.

Como um exemplo, podemos definir uma interface que provê dados de entrada. Para o código que vai usá-la não importa se os dados virão de uma base de dados, arquivo de texto, entrada padrão, etc. A restrição é que os métodos especificados pela interface sejam definidos.

public interface IDataReader {    
    enum Mode {
        MODE1,
        MODE2
    }
    int getValue(Mode mode);
}

Na nossa interface de exemplo, temos um método que retorna um valor de acordo com um modo.

Para facilitar os testes unitários, podemos usar o design pattern chamado injeção de dependência (dependency injection). Nesse padrão, temos um consumidor que faz uso da interface de maneira transparente e o injetor que escolhe a implementação dessa interface.

A ideia é que o injetor fique fora do consumidor, para que possamos controlá-lo sem mexer em seu código. Uma possibilidade é manter uma referência interna para a classe que provê a implementação, e que possamos setar esse membro externamente. Por exemplo, vamos criar a seguinte classe:

public class App {

    private IDataReader dataReader;
	
    public App(IDataReader dataReader){
        this.dataReader = dataReader;
    }
}

Note que passamos um objeto de uma classe que implementa IDataReader pelo construtor. Dessa forma, ao criar uma nova implementação para IDataReader (no nosso caso será o mock) não precisamos mexer em uma linha sequer do código de App.java.

Vamos adicionar o método mutiply, que fará uso da interface IDataReader. O código completo da classe App fica:

public class App 
{
    private IDataReader dataReader;
    
    public App(IDataReader dataReader){
        this.dataReader = dataReader;
    }
    public int multiply(){
        int value1 = dataReader.getValue(Mode.MODE1);
        int value2 = dataReader.getValue(Mode.MODE2);
        return value1 * value2;
    }
}

Neste exemplo de brinquedo, multiplicamos os resultados das chamadas do método getValue para cada um dos dois modos. Agora vamos criar a classe pare testar o método multiply.

public class AppTest {

    private IDataReader mockReader;
	
    @Before
    public void setup(){
        mockReader = EasyMock.createMock(IDataReader.class);
    }
	
    @Test
    public void testApp(){
				
        EasyMock.expect(mockReader.getValue(Mode.MODE1)).andReturn(6);
        EasyMock.expect(mockReader.getValue(Mode.MODE2)).andReturn(7);
		
        EasyMock.replay(mockReader);
		
        App tester = new App(mockReader);
		
        assertEquals("Result", 42, tester.multiply());
    }
}

No código acima, o membro mockReader contém uma referência para a implementação mock da interface IDataReader. O método setup anotado com @Before, é chamado antes de cada teste rodar e consiste em criar uma instância mock.

A sintaxe da função estática expect é meio estranha, mas podemos ler da seguinte maneira: estamos dizendo que quando o método getValue() for chamado com o parâmetro mode.MODE1, ele retornará 6, enquanto que com o parâmetro mode.MODE2() retornará 7.

Além disso, por padrão supõe-se que o método será chamado apenas uma vez. Se a chamada de tester.mutiply implica na chamada de getValue1 (getValue2) mais de uma vez, o teste apontará um erro. É possível especificar o número de vezes que o método será chamado, com o método times:

EasyMock.expect(mockReader.getValue(mode.MODE1)).andReturn(6).times(4);

Nesse caso o método deverá ser chamado no máximo 4 vezes. Se quisermos relaxar a condição de contagem de uso, podemos usar métodos como times(int min, int max), atLeastOnce() e anyTimes() [4].

O método estático replay serve para consolidar as definições feitas com o expect. Finalmente a implementação mock é passada como parâmetro para o construtor da classe App e podemos verificar se a multiplicação está se comportando como o esperado.

Para incluir o easymock como dependência do maven, basta incluir:

<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.1</version>
</dependency>

Referências

[1] http://www.vogella.de/articles/JUnit/article.html
[2] Wikipedia – Unit Testing
[3] Wikipedia – Dependency Injection
[4] Easy Mock Documentation
[5] Easy Mock Java Docs
[6] http://www.vogella.de/articles/EasyMock/article.html


Logging em Java e facade

Novembro 13, 2011

Nesse post vou falar sobre logging em Java, mais especificamente de SLF4J e comentar sobre o design pattern facade.

SLF4J

O SLF4J (abreviação de simple logging facade for Java) é um bom exemplo do design pattern facade (do francês fachada).

O padrão facade consiste basicamente de uma interface que abstrai a implementação. Isso é interessante para questões de manutenabilidade. Outra vantagem é que podemos utilizar diferentes implementações dessa interface sem ter que modificar o código que a utiliza.

Junto com a api do SLF4J, vem uma implementação padrão chamada slf4j-simple que simplesmente joga o logging para a saída padrão. Há outras implementações para essa api tais como:

  • log4j
  • logback
  • commons-logging
  • java.util.logging

Desses quatro, apenas o logback implementa nativamente as interfaces do SLF4J. Para as outras implementações, é necessária uma camada de adaptação. O diagrama de classes a seguir, retirado do manual do SLF4J, explica bem isso:

Fonte: http://www.slf4j.org/images/concrete-bindings.png

Diagrama (clique para ampliar)

Note que no caso da commons-logging, existe outra camada de adaptação. Isso porque na verdade, essa é uma implementação da api JLC (jakarta commons logging) da Apache. Assim, a primeira camada de abstração é uma conversão da SLF4J para a JLC, ficando a escolha da implementação da JCL a cargo da mesma.

A escolha da implementação do SLF4J é feita com base em qual delas é encontrada primeiro no classpath. Um exemplo básico de uso dessa api é:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = 
      LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

Se nenhuma implementação for encontrada no classpath, a seguinte mensagem de erro é exibida:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

e nesse caso, como podemos ver pelo diagrama, toda a saída é descartada.

Logback

A documentação do SLF4J é bem esparsa e não descreve a maior parte de suas funcionalidades. Como o logback implementa essa api, aproveitei que sua documentação é mais detalhada para entender melhor quais os principais aspectos do SLF4J.

Na verdade, o logback possui três módulos, o logback-core, o logback-classic e o logback-access. A implementação do SLF4J é dada pelo logback-classic que depende do logback-core.

Configuração

Embora a ideia da interface permita abstrair a implementação, pode haver particularidades de cada implementação que não são cobertas pela interface. Para isso, o logback utiliza arquivos de configuração.

O logbak utiliza uma configuração padrão, mas é possível sobrescrevê-la através de um arquivo xml que deve ser chamado logback.xml (ou logback-test.xml).

Para uma referência completa sobre o arquivo de configuração do logback, refira-se ao manual.

Níveis de logging

Há os seguintes níveis de logging: TRACE, DEBUG, INFO, WARN e ERROR. Existe um método na classe Logger para cada um desses níveis.

Quando uma mensagem é enviada através de um desses métodos, ela só é gravada se o modo no qual o logger foi setado for menor ou igual ao modo especificado pelo método. A ordem dos níveis é TRACE < DEBUG < INFO < WARN < ERROR.

Por exemplo, se escolhermos o modo do logger como INFO no arquivo de configurações,

<configuration>
    ...
    <logger name="main.App" level="INFO"/>
</configuration>

só as mensagens enviadas para métodos com nível acima ou igual a INFO serão gravadas, no caso .info(...) e .error(...).

package main;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    
    public static void main(String[] args) {
        Logger logger = 
            LoggerFactory.getLogger(App.class);
        logger.debug("A"); // não aparece
        logger.info("B"); // aparece
        logger.error("C"); // aparece
    }
}

Appender e Layout

Um appender basicamente diz aonde as mensagens de log deverão ser gravadas. Algumas saídas possíveis são console (stdout ou stderr), arquivo, banco de dados, servidor remoto, etc.

Já o layout define o formato de cada mensagem gravada. No logback é possível escrever seu próprio layout, mas a implementação padrão, o PatternLayout, é bem flexível. Ela utiliza variáveis (padrões) que são substituídas de acordo com a ocasião. Essas variáveis são precedidas pelo caractere %. Uma lista resumida de padrões dessa classe é a seguinte:

* %class – imprime o nome da classe de onde o método foi chamado
* %date (%d) – data atual (possível formatar)
* %level – o nível de logging
* %logger – imprime o nome do logger
* %line – número da linha de onde o método foi chamado
* %message (%msg) – a mensagem passada como parâmetro
* %thread – nome da thread
* %n – quebra de linha. Equivalente ao \n

Modificadores:

* %<valor> – padding de <valor> espaços à direita (ex. %-5msg)
* %<valor> – padding de <valor> espaços à esquerda (ex. %5msg)

Com isso podemos interpretar o que o trecho a seguir significa:

%d [%thread] %-5level %logger - %msg%n

Uma mensagem formatada com esse padrão consiste da data de quando a mensagem foi enviada, seguida do nível de logging do método, seguido do nome do logger e então a mensagem em si.

Para uma referência completa sobre os padrões e modificadores, consulte o manual.

Agora podemos configurar o logback para imprimir o log na saída de erro por exemplo,

<configuration>

  <appender name="my-apender" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
    <target>
      System.err
    </target>
  </appender>

  <logger name="main.App" level="TRACE">
    <appender-ref ref="my-apender" />
  </logger>

</configuration>

Basicamente a classe ch.qos.logback.core.ConsoleAppender refere-se ao console e a saída de erro é especificada pelo atributo target (padrão é stdout). Depois o appender é associado ao log “main.App”, que é o log que criamos num exemplo anterior. Para mais informações sobre o appender, veja o manual.

Mensagens parametrizadas

Uma funcionalidade provida pela SLF4J é o uso de mensagens parametrizadas para a melhoria de desempenho. Considere o seguinte caso:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

Embora a mensagem seja somente logada no modo de debug, o custo de construí-la (conversão do objeto para string e concatenação) será pago em qualquer modo. Uma alternativa é colocar um if, como no exemplo a seguir

if(logger.isDebugEnabled()) {
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

Além de deixar a sintaxe mais carregada, ainda temos um overhead adicional por conta da condicional. Podemos então usar mensagens parametrizadas da seguinte forma:

logger.debug("The entry is {}.", entry);

Nesse caso a substituição só é feita se o estiver no modo debug.

Leitura adicional

[1] História de logging em Java
[2] Manual do logback
[3] Reasons to prefer logback over log4j


Callbacks e uma solução em C++

Maio 8, 2011

O resolvedor de PLI Xpress possui algumas callbacks que são chamadas em pontos específicos do código. Existem callbacks que são chamadas cada vez que a relaxação linear é resolvida em um nó do branch-and-bound, logo depois de decidir qual variável sofrerá branching, quando encontra uma solução inteira, etc.

As callbacks nada mais são do que ponteiros para funções providas pelo usuário. Por exemplo, a callback que será chamada logo depois de uma variável ser escolhida para sofrer branching possui a seguinte forma:

void (*fucb)(XPRSprob my_prob, void *my_object, int *entity, int *up, double *estdeg)

Ou seja, precisamos passar uma função que recebe my_prob, que é um ponteiro para a estrutura do modelo atual; my_object é um ponteiro para um objeto fornecido pelo usuário; entity é o identificador da variável que vai sofrer branch (geralmente é o número da coluna do modelo); up indica se a variável será arredondada para cima ou para baixo; já estdeg (estimated degradation) indica quanto a função objetivo vai piorar ao fazer o arredondamento.

Para setar essa callback, devemos chamar a seguinte função da API do XPRESS:

int XPRSsetcbchgbranch(XPRSprob prob, void (*fucb)(XPRSprob my_prob, void *my_object, int *entity, int *up, double *estdeg), void *object);

Note que é aqui que devemos informar o endereço da estrutura que será passada para nossa função através de my_object.

Vamos a um exemplo minimal.

void observer(XPRSprob my_prob, 
              void *my_object, 
              int *entity,
              int *up, 
              double *estdeg){
    printf("%d\n", *my_object);
}
...
int obj = 3;
int XPRSsetcbchgbranch(prob, observer, &obj);

Aqui eu passo a variável obj ao setar a callback. Logo, toda vez que a função for chamada, ela imprimirá 3.

E se eu quiser fazer algo mais complicado, como por exemplo imprimir o nome da variável? Eu poderia definir um mapa que leva um índice de coluna no nome da variável.

Porém, é mais eficiente fazer esse mapeamento através de uma função. Minha variável tem o nome x(i,j) e a coluna é dada por col = i*n + j, onde n é o número de variáveis j. Para fazer o mapeamento reverso eu faço: i = col/n e j = col%n.

A função teria a seguinte cara:

void rev_map(int col, int *i, int *j){
    *i = col/n;
    *j = col%n;
}

A função foo a seguir simula a função de callback, que receberá um elemento *void, que nesse caso é um ponteiro para função.

int foo(void *obj){
    void (*ptr)(int col, int *i, int *j)
        = (void(*)(int col, int *i, int *j)) obj;
    int i, j;
    
    ptr(5, &i, &j);
    cout << "i: " << i << ", j: " << j << endl;
}

int main (){
    foo((void*) rev_map);
}

O único porém é que a função depende da variável n. Como fazer? Poderíamos fazer igual à API do XPRESS, colocando um parâmetro a mais em rev_map, e passando um parâmetro a mais para foo.

void rev_map(int col, int *i, int *j, void *ext){
    int *n = (int*)(ext);
    *i = col/(*n);
    *j = col%(*n);
}

int foo(void *obj, void *ext){
    void (*ptr)(int col, int *i, int *j, void *ext)
        = (void(*)(int col, int *i, int *j, void *ext)) obj;
    int i, j;
    
    ptr(5, &i, &j, ext);
    std::cout << "i: " << i << ", j: " << j << std::endl;
}

int main (){
    int n = 3;
    foo((void*) rev_map, &n);
}

O negócio começa a ficar feio…

Podemos usar uma classe para simplificar.

class rev_map {
    int n;
public:
    rev_map(int _n) : n(_n) {};
    void operator() (int col, int *i, int *j){
        *i = col / n;
        *j = col % n;
    }
};

A vantagem é que ela fornece uma função com estado interno (o membro n). Aí não precisa ficar passando o n como parâmetro pra lá e pra cá. Como essa classe é só um wrapper para uma função, achei mais conveniente fazer a sobrecarga do operator () do que definir um método. Esse tipo de classe também é conhecida como functor.

Objetos dessa classe são quase como funções, já que você pode invocá-los diretamente como no exemplo:

rev_map obj;
obj(col, &i, &j);

O resto do código fica:

int foo(void *obj){
    rev_map *x = (rev_map *) obj;
    int i, j;
    (*x)(5, &i, &j);
    std::cout << "i: " << i << ", j: " << j << std::endl;
}

int main (){
    int n = 3;
    rev_map x(n);
    foo((void*)&x);
}

Polimorfismo

Além da simplicidade, outra vantagem de passar objetos é fazer uso do polimorfismo, característico de orientação a objetos.

No meu código, eu escrevi uma classe encapsulando a API do Xpress, para facilitar a criação de modelos PLI, a qual chamei de Xpress. Ao criar um modelo novo, eu geralmente defino uma nova classe herdando de Xpress. Considere Model1 e Model2 dois tais modelos.

struct Xpress {

};
struct Model1 : Xpress {

};
struct Model2 : Xpress {

};

Cada modelo tem seu conjunto de variáveis, mas eu gostaria que a callback imprimisse o nome das variáveis de maneira transparente. A minha solução foi definir rev_map como classes internas a esses modelos, inclusive na classe Xpress, pois é essa classe que vai garantir a transparência.

struct Xpress {
    struct rev_map {
    };
};
struct Model1 : Xpress {    
    struct rev_map : Xpress::rev_map {
    };
};
struct Model2 : Xpress {
    struct rev_map : Xpress::rev_map {
    };
};

A vantagem de usar classes internas é que elas podem ter o mesmo nome. A desambiguação é sempre feita de acordo com o escopo. Para selecionar rev_map de uma classe específica, basta qualificá-lo como por exemplo Xpress::rev_map.

E mais, como iremos chamar o operator (), recebendo col, *i, *j, para todo objeto rev_map, devemos forçar que todas as classes derivadas de Xpress::rev_map implementem-no. Podemos fazer com que rev_map em Xpress se comporte como uma interface, tornando-a puramente virtual:

struct Xpress {
    struct rev_map {
        virtual void operator() 
            (int col, int *i, int *j) = 0; 
    };
};

O restante do modelo fica:

struct Model1 : Xpress {
    
    struct rev_map : Xpress::rev_map {
        void operator() (int col, int *i, int *j) {
            printf("model1\n");
        } 
    };
};
struct Model2 : Xpress {
    struct rev_map : Xpress::rev_map {
        void operator() (int col, int *i, int *j) {
            printf("model2\n");
        } 
    };
};

void foo(void *obj){

    Xpress::rev_map *x = (Xpress::rev_map *) obj;
    int i, j;
    (*x)(5, &i, &j);
}

Para verificar o polimorfismo em ação:

int main (){

    Model1::rev_map a;
    foo((void*)&a); // imprime model1
    Model2::rev_map b;
    foo((void*)&b); // imprime model2
}

Veja que a callback (foo) vai invocar corretamente as classes internas de Model1 e Model2, mesmo que em sua implementação seja usada a classe interna de Xpress.