Evitando duplicação com Strategy e Classes Anônimas

Suponha que temos a seguinte classe Desenho:

class Desenho {

	private final String nome;
	private final int decadaDeCriacao;

	public Desenho(String nome, int decadaDeCriacao) {
		this.nome = nome;
		this.decadaDeCriacao = decadaDeCriacao;
	}

	public String getNome() {
		return nome;
	}

	public int getDecadaDeCriacao() {
		return decadaDeCriacao;
	}
	
	public String toString() {
		return nome + " (" + decadaDeCriacao + ")";
	}
}

Com a classe Desenho em mãos, vamos criar uma classe principal e alguns desenhos, colocando-os em uma lista:

class Programa {
	public static void main(String[] args) {
		Desenho popeye = new Desenho("Popeye", 1920);
		Desenho picaPau = new Desenho("Pica-pau", 1940);
		Desenho flintstones = new Desenho("Flintstones", 1960);
		Desenho scoobyDoo = new Desenho("Scooby-Doo", 1970);
		Desenho simpsons = new Desenho("Simpsons", 1990);
                
		List<Desenho> desenhos = 
                Arrays.asList(popeye, picaPau, 
                            flintstones, scoobyDoo, simpsons);
	}
}

Essa lista de desenhos poderia, ao invés de ser criada na mão, vir de um banco de dados através de uma DAO.

Dentro da main, vamos filtrar todos os desenhos que foram criados antes de 1960 e imprimi-los:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getDecadaDeCriacao() < 1960){
		antesDe1960.add(desenho);
	}
}
for (Desenho desenho : antesDe1960) {
	System.out.println(desenho);
}

Deverá ser impresso:

Popeye (1920)
Pica-pau (1940)

E se quisermos filtrar todos os desenhos que começam com a letra “S”? Faríamos algo como:

List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
	if(desenho.getNome().startsWith("S")){
		comecamComS.add(desenho);
	}
}
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos a seguinte saída:

Scooby-Doo (1970)
Simpsons (1990)

Observem acima as linhas destacadas no código dos dois filtros. São as únicas linhas essenciais, com código que tem a ver com o que realmente queremos fazer: filtrar os desenhos.

O resto é código repetitivo, apenas dá suporte ao que queremos fazer. E esse código de suporte tem exatamente a mesma estrutura nos dois exemplos. Código duplicado, a raiz de todo o mal!

Evitando duplicação com Strategy e Classes Anônimas

Podemos evitar esse tipo de duplicação extraindo o código repetitivo para uma classe que filtra os desenhos:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(???){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Mas a comparação vai variar de acordo com o que quisermos filtrar. E agora?

Podemos utilizar um molde chamado Strategy, de maneira a isolarmos as diferentes comparações cada uma em uma classe própria, respeitando um contrato comum.

Generalizando: Strategy é a ideia de encapsular uma família de algoritmos intercambiáveis em objetos com um contrato comum.

Para definir um contrato em Java, utilizamos uma interface:

interface ComparacaoDeDesenhos {
	boolean valePara(Desenho desenho);
}

Devemos colocar a interface ComparacaoDeDesenhos como parâmetro do método filtra de FiltroDeDesenhos, usando-a para encapsular a lógica de filtragem dos desenhos de maneira polimórfica:

class FiltroDeDesenhos {
	public List<Desenho> filtra(List<Desenho> desenhos, 
				ComparacaoDeDesenhos comparacao){
		List<Desenho> filtrados = new ArrayList<>();
		for (Desenho desenho : desenhos) {
			if(comparacao.valePara(desenho)){
				filtrados.add(desenho);
			}
		}
		return filtrados;
	}
}

Para utilizarmos a nossa nova classe FiltroDeDesenhos para filtrar os desenhos criados antes de 1960, precisaríamos criar uma classe que atenda ao contrato definido pela interface ComparacaoDeDesenhos:

class ComparacaoDeDesenhosAntesDe1960 
				implements ComparacaoDeDesenhos {
	public boolean valePara(Desenho desenho) {
		return desenho.getDecadaDeCriacao() < 1960;
	}
} 

Então, poderíamos cria uma instância da nossa nova classe ComparacaoDeDesenhosAntesDe1960, e passá-la para o nosso FiltroDeDesenhos, depois imprimindo os desenhos filtrados:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

ComparacaoDeDesenhos comparacaoDeDesenhosAntesDe1960 = 
					new ComparacaoDeDesenhosAntesDe1960();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
					comparacaoDeDesenhosAntesDe1960);
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

A saída seria a mesma de antes:

Popeye (1920)
Pica-pau (1940)

Para filtrar os desenhos que começam com “S”, teríamos que criar a classe ComparacaoDeDesenhosQueComecamComS.

Mas será que, pra todo o tipo de comparação, temos que criar uma classe diferente? Se fosse assim, o número de classes ia ser imenso.

Ao invés de criar a classe ComparacaoDeDesenhosAntesDe1960, poderíamos criar uma instância de uma classe sem nome, que atende à interface ComparacaoDeDesenhos, tudo de uma vez:

FiltroDeDesenhos filtro = new FiltroDeDesenhos();

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
for (Desenho desenho : antesDe1960) {
       	System.out.println(desenho);
}

Continuaríamos com a mesma saída:

Popeye (1920)
Pica-pau (1940)

Observe demos um new na interface ComparacaoDeDesenhos, seguido de uma implementação do método valePara, sem fornecer um nome para a classe.

Ao criarmos uma implementação de uma interface que não tem nome, estamos criando uma classe anônima.

O trio “});” é bem familiar para quem usa Javascript, já que aparece nas funções de callback usadas por muitas bibliotecas.

Para filtrar todos os desenhos que começam com “S”, faríamos:

List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});
for (Desenho desenho : comecamComS) {
	System.out.println(desenho);
}

Teríamos exatamente a mesma saída de antes:

Scooby-Doo (1970)
Simpsons (1990)

Comparando as implementações

Se deixarmos a impressão dos desenhos filtrados de fora, teríamos o seguinte código antes do uso de Strategy com classes anônimas:

List<Desenho> antesDe1960 = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getDecadaDeCriacao() < 1960){
       		antesDe1960.add(desenho);
       	}
}
List<Desenho> comecamComS = new ArrayList<>();
for (Desenho desenho : desenhos) {
       	if(desenho.getNome().startsWith("S")){
       		comecamComS.add(desenho);
       	}
}

Já o código com Strategy e classes anônimas, ficou assim:

List<Desenho> antesDe1960 = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getDecadaDeCriacao() < 1960;
		}
});
List<Desenho> comecamComS = filtro.filtra(desenhos, 
	new ComparacaoDeDesenhos() {
		public boolean valePara(Desenho desenho) {
			return desenho.getNome().startsWith("S");
		}
});

O número de linhas ficou o mesmo, mas a modelagem da solução ficou melhor, evitando repetição do laço e do acúmulo dos desenhos filtrados em uma nova lista.

Uso de Strategy e Classes Anônimas em bibliotecas

É muito comum em bibliotecas do mundo Java utilizar o molde Strategy com classes anônimas.

Um exemplo bem comum é a interface Comparator da API de Collections do Java:

Collections.sort(desenhos, new Comparator<Desenho>() {
	public int compare(Desenho desenho1, Desenho desenho2) {
		return desenho1.getNome()
				.compareTo(desenho2.getNome());
	}
});
for (Desenho desenho : desenhos) {
	System.out.println(desenho);
}

O código acima, irá imprimir os desenhos ordenados por nome:

Flintstones (1960)
Pica-pau (1940)
Popeye (1920)
Scooby-Doo (1970)
Simpsons (1990)

Em frameworks como Spring, o uso de Strategy e classes anônimas é bastante comum. É usado, por exemplo, para gerenciar uma transação programaticamente:

transactionTemplate.execute(new TransactionCallback() {
	// código executado em uma transação
	public Object doInTransaction(TransactionStatus status) {
 		atualizarPedidos(compra.getPedidos());
		return atualizarTotalDaCompra(compra);
 	}
});

Concluindo

Através do uso de Strategy e classes anônimas, conseguimos criar uma solução que encapsula a estrutura, permitindo-nos focar na essência do que estamos fazendo e, dessa maneira, evitando duplicação.

Essa solução é bastante usada em bibliotecas e frameworks do mundo Java.

Porém, a sintaxe de classes anônimas em Java é assustadora.

No próximo post, veremos o que o Java 8 tem a oferecer para deixar nosso código mais sucinto.

O código desse post pode ser encontrado em: https://gist.github.com/alexandreaquiles/9644356

Sequências Infinitas em Scala e Java

Sequências Infinitas?

Em um workshop com o pessoal do trabalho, iríamos começar mais uma implementação de Fibonacci.

Acabamos definindo uma API parecida com a abaixo:

public List<Integer> fibonacci();

Porém, para conseguir fazer algo do gênero, é necessária uma maneira de representar uma sequência infinita de valores.

No workshop, como precisávamos de foco, mudamos a API para receber um número e retornar o número de Fibonacci correspondente.

Só que há uma maneira de representar essa sequência infinita em um computador…

Fibonacci?

Só para lembrar,  conforme está descrito na Wikipedia, a sequência de Fibonacci “é uma sequência de números naturais, na qual os primeiros dois termos são 0 e 1, e cada termo subsequente corresponde à soma dos dois precedentes”.

Lazy Evaluation

Na teoria de linguagens de programação, há o conceito de Lazy Evaluation, em que o runtime adia a avaliação de um trecho de código até quando esse código realmente precisar ser executado.

Esse conceito está presente principalmente em Programação Funcional, em linguagens como Haskell, Scheme ou Scala.

Em Scala

Com a linguagem de programação Scala, é possível criar uma  uma função recursiva que calcula a sequência de Fibonacci em que não há condição de parada!

def fibonacciSequence : Stream[Long] = {
    def fibonacciFrom(a: Long, b: Long) : Stream[Long] = a #:: fibonacciFrom(b, a+b)
    fibonacciFrom(0, 1)
}

Mas como isso é possível? Como o código acima não entra em recursão infinita até gerar um StackOverflowError?

No código acima, é utilizada uma estrutura de dados chamada Stream, que é uma lista cuja avaliação da concatenação (o #::) é lazy. Por isso, a chamada recursiva só é feita quando realmente é necessária.

Se chamarmos diretamente a função fibonacciSequence a partir do REPL do Scala será retornado:

scala> fibonacciSequence
> res0: scala.collection.immutable.Stream[Long] = Stream(0, ?)

Observe que só o primeiro valor, que no caso é 0, será retornado. O resto da sequência ainda não foi avaliado, o que é representado como ?.

Se utilizarmos a função take para pegarmos os 5 primeiros valores dessa sequência, ainda assim apenas o primeiro valor é avaliado:

scala> fibonacciSequence take 5
> res1: scala.collection.immutable.Stream[Long] = Stream(0, ?)

Para realmente avaliar os valores, temos que utilizá-los, por exemplo, tranformando para uma lista que não é lazy com um toList ou percorrendo-os com um foreach:

scala> fibonacciSequence take 5 toList
> res2: List[Long] = List(0, 1, 1, 2, 3)

scala> fibonacciSequence take 5 foreach println
> 0
| 1
| 1
| 2
| 3

Em Java

Recentemente, o polêmico Uncle Bob tem escrito sobre como utilizar conceito de Programação Funcional em Java e tem recebido diversas críticas.

Em um de seus artigos, Uncle Bob mostrou como utilizar lazy evaluation em Java utilizando um Iterator. Há um exemplo mostrando o uso de Iterator para criar uma sequência infinita de inteiros elevados ao quadrado.

Na verdade, Neal Ford já havia mostrado como usar um Iterator para obter lazyness com Java. O exemplo utilizado foi o cálculo de números primos.

Com Iterator

Através da técnica do Iterator, é possível fazer um código bem simples para gerar uma sequância “infinita” com os números de Fibonacci:

public class FibonacciSequence implements Iterator<Long> {
    private long n = 0;
    private long a = 0;
    private long b = 1;

    public boolean hasNext() { return a + b > 0; }

    public Long next() {
        long fib;
        if(n == 0 || n == 1) {
            fib = n;
        }
        else {
            fib = a + b;
            a = b;
            b = fib;
        }
        n++;
        return fib;
    }

    public void remove() {}
}

As variáveis a e b armazenam os valores de Fibonacci dos números anteriores. O número atual é mantido na variável n.

Os dois primeiros valores da sequência 0 e 1, são retornados diretamente, sem atualizar as variáveis a e b.

O Fibonacci de um dado número n é a soma dos números de Fibonacci precedentes, portando é a + b.  Para preparar a sequência para os próximos valores, as variáveis a e b são atualizadas com o número de Fibonacci anterior e o atual, respectivamente.

A implementação do método hasNext considera se a soma de a + b ainda é positiva, para evitar Integer Overflow, o que faria com que os números a e b ficassem com valores negativos.

Podemos utilizar o código acima, por exemplo, para pegar os 10 primeiros números na sequência:

FibonacciSequence fs = new FibonacciSequence();
for(int i = 0; i < 10 && fs.hasNext(); i++){
    System.out.println(fs.next());
}

Podemos criar o objeto Taker criado por Uncle Bob para simular a função take do Scala:

public class Taker<T> {
    public List<T> take(int n, Iterator<T> xs) {
        List<T> taken = new ArrayList<T>();
        for (int i = 0; xs.hasNext() && i < n; i++) {
           taken.add(xs.next());
        }
        return taken;
    }
}

Se utilizarmos o Taker para gerar a sequência dos 25 primeiros números de Fibonacci, teríamos o seguinte código:

List<Long> fibonacciUpto25 = new Taker<Long>().take(25, new FibonacciSequence());

Com Functional Java

A biblioteca Functional Java tem uma estrutura de dados Stream parecida com a do Scala, que permite lazy evaluation.

Baseados em um exemplo da documentação da biblioteca, poderíamos definir uma variável no nosso programa que contém a sequência de Fibonacci:

public static final Stream<Long> fibonacciSequence = new F2<Long, Long, Stream<Long>>() {
    public Stream<Long> f(final Long a, final Long b) {
        return Stream.cons(a, curry().f(b).lazy().f(a + b));
    }
}.f(0L, 1L);

O método cons da classe Stream é correspondente ao #:: do Scala.

A classe Stream provê um método take. Assim, para obter nossa lista com os 25 primeiros números da sequência de Fibonacci:

List<Long> fibonacciUpto25 = fibonacciSequence.take(25).toList();

Com Totally Lazy

Já a biblioteca Totally Lazy tem uma função embutida na classe Numbers que gera a sequência de Fibonacci. Essa função é implementada da seguinte forma:

public static Sequence<Number> fibonacciSequence  =
computation(Pair.<Number, Number> pair(0, 1), reduceLeftShift(sum)).map(first(Number.class));

Para obter nossos 25 primeiros números de Fibonacci:

List<Number> fibonacciUpto25 = fibonacciSequence.take(25).toList();

A abordagem do Totally Lazy é bem diferente das demais.  Observe que a classe Number é utilizada, ao invés de Long.

Maven, a Super Nanny dos projetinhos rebeldes

A melhor coisa a fazer enquanto o maven baixa a internet é ler a biografia gloriosa dos criadores do maven #not (via @elizario)

Tem gente que odeia o Maven. Maven é autoritário, inflexível, de opinião forte.

Os caras da ThoughtWorks estão nessa turma. Dan North, thoughtWorker e criador do BDD, sempre caçoa do Maven em suas palestras. Uma das características típicas de um thoughtWorker de acordo com a página de recrutamento da TW é “enjoying complaining about Maven”.

O Bundler seguiu a parte de gerenciamento de dependências do Maven. E tá sendo zoado no Twitter.

_________

Um bom processo de build precisa:

  • compilar o código (no caso de Java)
  • integrar com bibliotecas de terceiros
  • executar os testes automatizados (unitários/funcionais)
  • gerar entregáveis
  • gerar documentação
  • analisar (estaticamente) código

Apesar de chato, Maven traz para projetos Java convenções para cada uma dessas tarefas.

As convenções podem ser um pouco esquisitas, o XML pode ser feio e as coisas podem parecer feitas por magia negra. Talvez vá longe demais definindo como organizar as pastas do seu projeto.

Mas Maven resolveu vários problemas que sempre aconteciam ao construir projetos em Java, fornecendo soluções out-of-the-box. Veio a calhar na Apache Foundation. A parte de gerenciamento de dependências levou a criação o Apache Ivy, que cuida só desse aspecto (o que é uma ótima idéia).

Maven ajuda a domar projetinhos rebeldes . E é melhor ainda para educar o projeto desde o nascimento.

Bons programadores tem o seu potencial melhor utilizado se não precisarem ficar recriando scripts de build/release. A lição do Maven é: “Automatize o que não for criativo. E use convenções”.

Build do Eclipse não vale!

Um anti-pattern que já vi muito por aí nos projetos Java em que participei é o Build do Eclipse.

Ao invés de usar um script de build automatizado para criar um pacote entregável (JAR, WAR, EAR), o sistema é colocado em produção simplesmente copiando os .class criados pelo Eclipse. Também são copiados o .project, .classpath e .settings e, às vezes, até as pastas de controle versão (.svn).

O Eclipse é ótimo para ajudar no desenvolvimento com seu code-completion, recompilação a cada arquivo salvo, sugestões, code snippets, atalhos de teclado e o montão de plugins. Também é excelente para refatorar de maneira eficiente e segura. E até para disparar um script de build.

Mas o Eclipse não deve ser usado como ferramenta de build.

Por que não?

  1. Porque builds tem que ser automatizados
  2. Usar builds do Eclipse, é um forte indicador de que você não automatiza seus builds.

    OK, você pode automatizar o build criando um sistema que abre o Eclipse, dispara os cliques necessários e copia os arquivos. Mas ninguém faz isso. E, convenhamos, essa ideia é estúpida. Usar uma ferramenta de build especializada como Ant ou Maven é muito, mas muito, mais simples.

    E por que automatizar seus builds é bom?

    Builds são receitas de bolo. São trabalhos repetitivos e tediosos. Não são processos criativos. Humanos são bons em criar e não em repetir.

    Com menos intervenção humana, seus builds terão menos erros. E você liberará tempo para trabalho criativo, que é o que os seus programadores deveriam estar fazendo.

  3. Porque você precisa usar sempre a mesma IDE
  4. Bons programadores Java tem opinião forte sobre qual IDE é melhor. Se você forçá-lo a usar uma IDE diferente, você fará um programador menos feliz e programadores bons precisam estar felizes para produzir código de qualidade.

    Pode parecer besteira, e talvez seja. Mas restrições de IDE são desnecessárias. Com um bom script de build, você se liberta de qualquer acoplamento com uma IDE específica e permite flexibilidade na hora de desenvolver.

  5. Porque você não está integrando continuamente
  6. Integração Contínua é uma das práticas mais interessantes (e usadas) do XP. Integrar continuamente é bom em qualquer contexto e em qualquer metodologia. E ajuda bastante na diminuição de vários riscos de projetos de software como descoberta tardia de problema de integração e defeitos, falta de código entregável e baixa qualidade interna.

    Mas para integrar continuamente você precisa ter, no mínimo, builds automatizados. Se o seu servidor de Integração Contínua precisa do Eclipse instalado, isso é um mal sinal.

    Para aproveitar todo o valor da Integração Contínua, além de builds automatizados, é importante que você tenha testes automatizados e análise estática de código. Assim, você testará seu código constantemente e evitará que código sem testes, duplicado e complexo entre em seu repositório.

Generalizando
Na verdade, um nome melhor para esse anti-pattern seria Build da IDE, porque o mesmo vale para Netbeans, IDEA ou qualquer outra IDE.

Mas, enfim, build do Eclipse não vale!