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?


Introdução a Guava – Revista Java Magazine 104

Junho 24, 2012

Este mês foi publicada na revista Java Magazine uma matéria que escrevi sobre o projeto Guava. O mais legal é que o convite foi feito por causa dos posts sobre Java que escrevi aqui no blog.

Basicamente o Guava é composto por um conjunto de bibliotecas que visam simplificar a vida do desenvolvedor. Ela foi inicialmente desenvolvida pelo Google e depois teve seu código-fonte disponibilizado.

No artigo descrevemos um probleminha simples e apresentamos uma solução que utiliza diversas bibliotecas do Guava. Usamos uma extensão da Collections do SDK, utilitários para estruturas de dados do SDK como List e Map, novas estruturas de dados como muli-mapas, manipulação de String e IO, funções que visam tornar expressões matemáticas mais legíveis, etc. Na solução também aplicamos um paradigma funcional para demonstrar o uso das classes Function e Predicate que podem simplificar e melhorar a legibilidade do código.

O conteúdo completo é restrito a assinantes, mas uma prévia está disponível aqui.

Acabei deixando alguns componentes do Guava de fora como Cache, Concorrência e EventBus, mas algum dia pretendo estudá-los.

Conclusão

Embora a escrita de um artigo para uma revista tome mais tempo que a de um post do blog, o conteúdo fica com uma qualidade melhor porque passa por revisão. Além disso, aprendi um assunto que eu vinha querendo estudar faz um tempo. Enfim, gostei da experiência e pretendo escrever novos artigos.


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


Java Servlets no Tomcat

Dezembro 11, 2011

Nesse post vou comentar um pouco sobre Servlets e um exemplo básico de utilização usando o Tomcat.

O que são Servlets?

Segundo a Wikipedia, um Servlet é uma classe Java usada para estender a funcionalidade de um servidor, sendo capaz de receber e enviar mensagens via um protocolo cliente-servidor (em geral HTTP).

Aplicações que utilizam a API de Servlets podem ser disponibilizadas na web através de um Web Container. Há diversos Web Container’s grátis, como por exemplo, o GlassFish (da própria Oracle), o JBoss AS (da Red Hat) e o Tomcat (da Apache). Podemos pensar em Web Conteiner’s como bibliotecas que implementam a API de Servlets.

A versão da API de Servlets que vamos utilizar é a 3.0 (Java EE6). Essa versão é suportada pelas seguintes versões dos Web Container’s citados acima: GlasshFish +3, JBoss AS +7 e Tomcat +7. Em nosso exemplo usaremos o Tomcat 7.

O build e o deploy do código serão feitos através do Maven.

Tomcat 7

Antes de mais nada, vamos instalar e configurar o Tomcat 7. Para instalar, basta baixar os .jar diretamente do site do Tomcat em um diretório qualquer. A partir de agora vamos nos referir ao local de instalação como $TOMCAT_DIR. Na pasta $TOMCAT_DIR/bin, podemos iniciar o Tomcat através do script startup.sh.

Para verificar se o Tomcat foi corretamente iniciado, podemos entrar no endereço localhost:8080.

Podemos gerenciar o Tomcat via web, mas para isso, precisamos adicionar um usuário no arquivo $TOMCAT_DIR/conf/tomcat-users.xml. Nesse arquivo xml, adicione a seguinte entrada dentro da tag <tomcat-users>:

<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="nome-usuario" password="senha" roles="manager-gui, manager-script"/>

trocando "nome-usuario"/"senha" por valores adequados. É preciso reiniciar o Tomcat (shutdown.sh + startup.sh). Agora você deverá poder acessar a página localhost:8080/manager com o usuário e senhas cadastrados.

Servlet Java

A estrutura de diretórios do nosso exemplo é a seguinte:

|-- pom.xml
 `-- src
     `-- main
         |-- java
         |   `-- me
         |       `-- kuniga
         |           `-- main
         |               `-- MyApp.java
         |-- resources
         |
         `-- webapp
             `-- WEB-INF

A implementação da nossa única classe é baseada em [4]. Uma das novidades da API de Servlets 3.0 é o uso de anotações para configuração, como podemos ver no código a seguir:

@WebServlet("/MyApp")
public class MyApp extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,
                       HttpServletResponse response)
      throws ServletException, IOException {

    String nome = request.getParameter("nome");

    PrintWriter out = response.getWriter();
    out.println("Ola: " + nome);
  }
}

O argumento passado para a anotação WebServlet definirá a url de acesso à nossa aplicação. Já o método doGet será invocado quando uma requisição do tipo GET for feita. O conteúdo passado para o PrintWriter será retornado para o cliente.

Para compilar o código acima, devemos incluir a API 3.0 de Servlets. Podemos fazer isso incluindo uma dependência das APIs do Java EE6:

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-api</artifactId>
  <version>6.0</version>
  <scope>provided</scope>
</dependency>

É importante setar o escopo como provided para que o maven não coloque o .jar contendo as APIs do Java EE6 no arquivo .war. Isso porque no Tomcat esse .jar já está presente e a duplicata ocasiona erro. (Aliás, uma dica para debugar erros é iniciar o Tomcat através do comando ./catalina.sh run ao invés do ./startup.sh, pois desta forma as mensagens de log são exibidas no terminal).

Configurando plugins do Maven

Basicamente o que o maven fará é compilar o programa e compactá-lo em um arquivo .war e então fará o deploy desse arquivo no Tomcat.

Vamos primeiramente configurar o plugin maven-war-plugin, que é responsável por gerar o arquivo .war. Em versões anteriores da API de Servlets, era necessário um arquivo de configuração chamado web.xml que ia em webapp/WEB-INF. Agora, a configuração básica pode ser feita através de anotações na classe do servlet e o arquivo web.xml não é obrigatório.

Por padrão, o plugin maven-war-plugin gera um erro na ausência do web.xml, mas podemos modificar esse comportamento através da seguinte configuração no pom.xml:

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>

O outro plugin que iremos configurar é o tomcat-maven-plugin, responsável pelo deploy no tomcat. Para isso, ele precisa da url de acesso ao manager, bem como um login válido para acessá-la. Podemos passar os dados de login através do pom.xml.

Mais especificamente, adicionamos uma entrada no settings.xml do maven, que se encontra em ~/.m2/settings.xml (mais informações sobre esse arquivo aqui).

<server>
  <id>mytomcat</id>
  <username>nome-usuario</username>
  <password>senha</password>
</server>

Onde id é um identificador qualquer, e username e password são aqueles que usamos no cadastro de usuário no Tomcat. No pom.xml do projeto passamos essa informação de login e a url da página:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>tomcat-maven-plugin</artifactId>
  <configuration>
    <url>http://localhost:8080/manager/html</url>
    <server>mytomcat</server>
  </configuration>
</plugin>

A versão completa do pom.xml está aqui e também disponibilizei o projeto todo no github.

Agora podemos executar os seguintes goals do maven:

> mvn clean package tomcat:deploy

Rodando a aplicação

Para testar a aplicação em funcionamento, podemos acessar a seguinte url: http://localhost:8080/kservlet/MyApp?nome=mundo

Referências

[1] Java Servlet – Página oficial da Oracle
[2] Java Servlet – Wikipedia
[3] How do I deploy a maven web application to Tomcat?
[4] Blog Caelum – Java EE6: Começando com as Servlets 3.0
[5] Tomcat 6.0.32 + Maven undeploy via script not working


Introdução ao Drools Planner

Novembro 20, 2011

O Drools Planner é um framework Java para resolução de problemas de planejamento através de meta-heurísticas, voltado especialmente para problemas de grande porte e alta complexidade (um número grande de restrições), que inviabilizam uma abordagem exata.

Além de prover um framework que fatora partes comuns a essas meta-heurísticas, o que permite reuso de código, o grande atrativo do Drools Planner é sua integração com o sistema de regras do Drools, o Drools Expert, que permite incluir restrições através de uma linguagem de alto nível.

O Drools Planner também provê uma ferramenta de benchmark, possibilitando a comparação, através de gráficos, entre os diversos algoritmos implementados com parâmetros diferentes, de modo a se escolher aquele que mais se adequa ao problema em questão.

Atualmente o Drools Planner implementa apenas algoritmos de busca local, entre eles: busca tabu, simulated annealing e great deluge.

Nota: A versão do Drools descrita nesse post é a 5.3.0 Final. Vale ainda ressaltar que como o Drools Planner em particular está em um estágio inicial, a API ainda está mudando bastante. Portanto, parte do que está escrito aqui pode não fazer sentido para versões anteriores.

Conceitos Básicos

Vamos discutir agora as principais características do Drools Planner.

Interfaces a serem implementadas

Solução. A solução de um problema é uma classe que representa as possíveis soluções do problema e deve implementar a interface Solution. Nessa classe geralmente incluímos referências a todas as informações que podem ser utilizadas pelo algoritmo.

Vizinhos. Para implementar uma busca local precisamos gerar soluções vizinhas a partir de uma dada solução. Cada tipo de movimento para gerar uma solução vizinha deve implementar a interface Move.

Vizinhança. No Drools Planner existe o conceito de fábrica de movimentos que, dada uma solução S, gera um conjunto de Moves, que estarão disponíveis para o algoritmo utilizar. Uma fábrica de movimentos deve implementar a interface MoveFactory.

Avaliação de uma solução com Drools Expert

A grande sacada do Drools Planner é usar o Drools Expert para associar um valor numérico a uma solução. Basicamente, o Drools Expert é uma linguagem de programação baseada em regras. Existe o conceito de working memory, onde diversos objetos, denominados fatos, estão inseridos. Num arquivo .drl, definimos as regras.

Uma regra utiliza a seguinte sintaxe:

rule "Nome da regra"
when
    // Conjunto de condicionais
then
    // Ação a ser tomada (sintaxe Java)
end

Quando um dado conjunto de fatos passa a satisfazer as condições da regra, a ação definida é executada. A vantagem é que em geral restrições são bem fáceis de serem modeladas com regras.

A ideia é escrever um regra que seja satisfeita quando uma restrição é violada. A ação a ser tomada é basicamente somar uma pontuação proporcional à importância dessa restrição. O algoritmo tratará de minimizar essa pontuação através da busca local.

Entidades de Planejamento

No Drools 5.3.0 foram introduzidos conceitos como Planning facts, entities, variables e value range.

Planning fact. É uma classe de fatos que são usados para calcular a pontuação, mas não são modificados ao longo do algoritmo.

Planning entity. É uma classe de fatos que são usados para calcular a pontuação, mas podem ser modificados pelo algoritmo. Essa classe deve ser anotada com @ PlanningEntity.

Planning Variable. São os membros de uma planning entity que podem ser modificados pelo algoritmo. Os getters desses membros devem ser anotados com @PlanningVariable.

Planning Value Range. São os possíveis valores que uma dada planning variable pode assumir. Geralmente esses valores são os elementos de uma lista na classe representando uma solução e nesse caso o getter dessa planning variable também deve conter a anotação @ValueRangeFromSolutionProperty.

Mais informações no manual.

Heurísticas Construtivas

A definição das entidades de planejamento (planning entities) permitem automatizar a geração de uma solução inicial através de heurísticas construtivas padrões como First Fit e Best Fit.

É possível definir sua própria heurística para construir a solução inicial. Para isso, basta o algoritmo implementar a interface CustomSolverPhaseCommand.

Arquivo de Configuração

Um outro item essencial do Drools Planner é o arquivo de configuração. Esse arquivo é no formato XML e define diversas características do algoritmo. Vamos destacar algumas delas.

Estrutura básica do arquivo de configuração

Ambiente de execução

A propriedade environmentMode define o ambiente de execução em que o algoritmo executará. Isso afeta o nível das mensagens de log e a checagem ou não de assertivas. Alguns valores mais comuns dessa propriedade são: DEBUG, REPRODUCIBLE e PRODUCTION.

Para mais informações, consulte o manual.

Classes da solução e da entidade de planejamento

É preciso informar qual classe implementa a solução do problema e qual representa a entidade de planejamento através das propriedades solutionClass e planningEntityClass, respectivamente.

Arquivo de regras

Através da propriedade scoreDrl, informamos o caminho para o arquivo de regras do Drools Expert.

Definição do tipo de pontuação

É possível trabalhar com diferentes tipos de pontuação especificando a propriedade scoreDefinition. As principais são SIMPLE e HARD_AND_SOFT. O primeiro é simplesmente um inteiro e o segundo um par de inteiros, sendo que um representa a parte “hard” e a outra “soft”. Uma pontuação “hard” não nula representa uma solução inviável e portanto tem peso infinitamente maior do que a pontuação “soft”.

A ideia de ter uma pontuação “hard” é permitir que o algoritmo trabalhe com soluções inviáveis e dessa forma conseguir escapar de ótimos locais no espaço de soluções viáveis.

Critério de Parada

A propriedade termination define as condições de parada do algoritmo, como por exemplo tempo de execução, número de iterações sem melhoria da solução, valor de solução alcançado, etc. Para mais detalhes, consulte o manual.

Escolha da heurística construtiva

Através da propriedade constructionHeuristic podemos definir as heurísticas usadas para a construção da solução inicial. Como dito anteriormente, algumas delas são FIRST_FIT e BEST_FIT. Mais informações no manual.

Se você optou por implementar sua própria heurística, a propriedade customSolverPhase deve ser utilizada. Mais detalhes no manual.

Configuração da Busca Local

Há três propriedades principais que precisamos para configurar a busca local: selector, acceptor e forager.

Selector. Define quais fábricas de movimentos serão utilizadas

Acceptor. Define qual algoritmo utilizar, bem como possíveis parâmetros do algoritmo escolhido. Por exemplo, o seguinte trecho de configuração:

<acceptor>
  <completeSolutionTabuSize>
    100
  </completeSolutionTabuSize>
</acceptor>

especifica que usaremos uma busca tabu que possui um conjunto de soluções tabu de tamanho 100 (ou seja, guarda as últimas 100 soluções encontradas e não deixa o algoritmo voltar nelas).

Forager. Especifica a escolha do movimento. As fábricas de movimentos gerarão muitos movimentos a partir da solução corrente. A propriedade minimalAcceptedSelection define que apenas um subconjunto desses movimentos sejam analisados para melhorar a eficiência.

Já a propriedade pickEarlyType permite parar de analisar os vizinhos assim que encontrar um satisfazendo alguma condição (como por exemplo algum que tenha pontuação melhor que a solução corrente).

Para mais detalhes sobre essas três propriedades, consulte no manual.

Conclusão

Pelo que estudei do Drools Planner até agora, consigo ver as seguintes vantagens:

* Parte comum das meta-heurísticas já está implementada
* Sistema de regras é escalável
* Fácil adicionar novas restrições se não precisar mudar as entidades

Já as desvantagens:

* Debugar código Drools é mais difícil do que debugar código Java (principalmente a parte do “when“)
* Paradigma diferente (programação declarativa)


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


Maven

Outubro 2, 2011

Uma ferramenta que conheci recentemente é o apache maven, que serve para gerenciar projetos java, como por exemplo compilar código java, gerar documentação, fazer deploy de aplicações web etc.

O objetivo desse post é cobrir os principais conceitos dessa ferramenta, apontando referências para quem quiser se aprofundar mais. Me baseei na versão mais recente do maven, o maven 3 (versão 3.0.3 mais especificamente).

O arquivo pom.xml

O maven usa um arquivo de configuração chamado Project Object Model (POM) para determinar as características do projeto e suas dependências.

Maven usa o paradigma de convenção ao invés de configuração. Isso quer dizer que, a menos que explicitado de outra forma, ele supõe configurações padrões (como por exemplo a localização dos códigos-fontes), o que permite que o arquivo pom.xml fique bem pequeno.

Um exemplo de um arquivo pom.xml é o seguinte:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>br.com.organizacao</groupId>
  <artifactId>teste</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
</project>

No exemplo acima, modelVersion define a versão da estrutura POM utilizada e a versão atualmente usada pelo maven é a 4.0.0. Segundo o blog da Caelum [2], os seguintes parâmetros são suficientes para identificar unicamente um projeto:

  • groupId – um identificador da empresa/grupo ao qual o projeto pertence. Geralmente o nome do site da empresa/grupo ao contrário;
  • artifactId – o nome do projeto;
  • version – a versão atual do projeto (PS: o modificador SNAPSHOT indica que essa versão é de desenvolvimento);

Além disso, no livro Maven By Example [1], eles definem o conceito de coordenadas maven, que é um conjunto de informações utilizadas para se referir a um projeto. Além das três propriedades acima, inclue-se o parâmetro packaging, que definirá se o pacote será comprimido em um jar, war, ear, etc. Dessa forma, o projeto acima seria referido por: br.com.organizacao:teste:jar:1.0-SNAPSHOT.

O maven implementa um mecanismo de herança. Isso quer dizer que um dado POM herda as configurações de seus ancestrais podendo sobrescrevê-las. Existe um POM que é ancestral de todos os POMs, o chamado super POM, de onde se herdam as configurações padrões.

Outro mecanismo é o de agregação, onde um POM principal pode definir módulos (sub-projetos) associados a outros POMs, que serão chamados pelo POM principal. Em [4], há um tópico detalhando e fornecendo exemplos para esses mecanismos.

Plugins

Se formos ver, o tamanho do maven é bem pequeno. Isso porque a maioria de suas funcionalidades é provida através de plugins. Um plugin consiste basicamente de uma coleção de goals. A sintaxe para executar um goal é

$mvn <plugin>:<goal>

Um exemplo é o plugin compiler, que possui o goal compile. Esse goal compila o código java e pode ser invocado da seguinte maneira:

$mvn compiler:compile

Repositório central do maven

Tanto os plugins quanto as bibliotecas mais comuns são mantidas em um repositório remoto central, chamado Maven Central Repository. Quando há uma dependência, quer de um plugin ou de uma biblioteca, o maven consulta esse repositório e cuida de fazer o download automaticamente.

Os objetos baixados são armazenados em um repositório local, que fica geralmente no diretório ~/.m2. Quando fazemos o build de um projeto nosso com o maven, ele também é copiado para esse diretório.

Ciclo de Vida do Build

O maven utiliza o conceito de ciclos de vida, que são compostos por uma sequência ordenada de fases. Há três ciclos de vida pré-existentes no maven: clean, default e site. O ciclo de vida default é composto pela sequência de fases: validate, compile, test, package, integration-test, verify, install, deploy (na verdade essa sequência está simplificada – a lista completa está nessa referência). Essas fases são sempre executadas nessa ordem. Assim, se executarmos a fase

$mvn install

Todas as fases que são anteriores a install serão executadas. Se quisermos executar o ciclo de vida default completo, basta executar a última fase, que é o deploy.

Na verdade uma fase não é executada, mas sim os goals associados a elas. Alguns goals já vêm associados por padrão a algumas fases. Por exemplo, ao declararmos o tipo de packaging como jar, o goal compiler:compile é associado à fase compile do ciclo de vida default. Para saber quais goals estão associados a quais fases por padrão, consulte essa referência.

Podemos associar mais goals às fases de build especificando no próprio arquivo pom.xml. Em [5], supõe-se a existência de um plugin chamado display-maven-plugin, que tem o goal time, que imprime o horário atual na saída padrão. Se quisermos imprimir o horário após a fase de testes terem sido executados, podemos associar esse goal à fase test, como no exemplo a seguir:

...
 <plugin>
   <groupId>com.mycompany.example</groupId>
   <artifactId>display-maven-plugin</artifactId>
   <version>1.0</version>
   <executions>
     <execution>
       <phase>test</phase>
       <goals>
         <goal>time</goal>
       </goals>
     </execution>
   </executions>
 </plugin>
...

Note que os conceitos de ciclos de vida e fases são uma maneira de organizar os diversos goals usados no processo de build. Em teoria poderíamos especificá-los explicitamente e estes seriam executados na ordem em que foram passados à linha de comando.

Plugin para o eclipse

Finalmente, para quem usa eclipse, existe um plugin para integração com o maven. A versão do eclipse que estou usando é a 3.7 (Indigo) e o plugin é o m2eclipse.

Para instalar o plugin, vá em Help > Install New Software…

Na barra de endereços coloque: http://download.eclipse.org/technology/m2e/releases

Selecione o item Maven Integration For Eclipse e siga as instruções até concluir a instalação. Algumas funcionalidades que esse plugin oferece são:

Importar um projeto que usa maven, no eclipse. O plugin utiliza informações do pom.xml para isso. Depois de instalado o plugin, vá em:

File > Import… > Maven > Existing Maven Projects

Next, preencha o campo Root directory e então Finish. Pronto, o projeto deve ter sido importado.

Uma alternativa é usar o plugin do próprio maven, o eclipse, para gerar os .classpath e .project. Para isso basta fazer

mvn eclipse:eclipse

Depois é só importar o projeto no eclipse (General > Existing Projects into Workspace).

Fazer o build do projeto dentro do eclipse.

Figura 1: fases do maven providas pelo plugin m2e

Há algumas fases pré-existentes providas pelo plugin, como as da Figura 1. Entretanto, se quisermos personalizar o build, basta ir em:

Run > Run As… > Maven build…

Podemos configurar o build do maven, ajustando os goals ou fases (e.g. clean, package, install, compiler:compile) e definindo opções como modo offline (opção -o), pular testes (opção -Dmaven.test.skip=true), etc.

Para um tutorial mais completo, refira-se a [3], que trata de assuntos como a criação de projetos usando o plugin archetype do maven, importação de projetos (inclusive via checkout de um repositório remoto), etc.

Leitura adicional

[1] Livro Maven by Example
[2] Processo de build com o Maven
[3] Introduction to m2eclipse
[4] Introduction to the POM
[5]Introduction to the Lifecycle