Domain Specific Languages em ação

Por Paulo Silveira em 21/09/07

Em diversos momentos sentimos a necessidade de utilizar uma linguagem para atacar um problema mais específico. Utilizar Java ou C# nesse tipo de problema pode gerar uma enorme quantidade desnecessária de código. Veja um exemplo que passamos na Caelum:

Set<Strategy> strategies = new HashSet<Strategy>();
Indicator<Double> close = new ClosePriceIndicator(timeSeries);
for (int i = 1; i <= 50; i++) {
  Indicator<Double> tracker = new EMAIndicator(close, i);
  Strategy strategy = new 
    IndicatorCrossedIndicatorStrategy(close, tracker);
  strategies.add(strategy);
}

No nosso caso, esse trecho de código deve ser compreensível para analistas de negócio, que não são necessariamente programadores, muito menos possuem conhecimento de Java.

Domain Specific Languages é o nome dado a prática de se criar pequenas linguagens para resolver um problemas bem específicos. Elas existem em dois sabores: as externas, que criam uma linguagem própria, e as internas, que na verdade utilizam um subconjunto de instruções de um linguagem já existente e utilizada no sistema.

Entre alguns clássicos exemplos de DSLs internas temos o uso da Criteria do hibernate, o uso do ruby nos arquivos de build do rake. Entre as DSL externas, temos as macros do excel e o xml do ant.

Utilizando a api de scripting do java 6, passamos a usar ruby (através da JRuby) para escrever essa parte da lógica de negócios, e nosso código em java ficou assim:

(1..50).collect{|x|
	Tail::IndicatorCrossedIndicatorStrategy.new(close,
		Tail::EMAIndicator.new(close, x))
}

Criando algumas factories, conseguimos chegar a um código muito mais simples:

(1..50).collect{|x|
	cross(close, ema(x))
}

Logo, estamos próximos de chegar a algo parecido com uma linguagem natural:

x de 1 a 50
       quando cruzar (fechamento, ema(x))

Para tal, uma das possibilidades seria usar um dos compiladores de compiladores existentes para Java, porém isso daria muito trabalho.

O Rodrigo Kumpera sugeriu escrever o próprio parser, como fez o Gilad Bracha no Small Talk. Tanto o Rodrigo quanto o Renato Lucindo citaram o spirit++, que faz isso para C++. Uma pena não existir algo equivalente para o Java.

Phillip Calçado recomendou fazer um teste para saber se devemos ou não melhorar ainda mais essa DSL. O teste consiste em colocar a linguagem natural ao lado da DSL ruby e ver se o especialista consegue fazer a ponte entre uma e outra. Exemplo:

(1..50).collect{|x|
	cross(close, ema(x))
}

x de 1 a 50
       quando cruzar (fechamento, ema(x))

Se o especialista no domínio não entender a semelhança entre os dois códigos, é necessário aprimorar a DSL em questão.

Primeira turma de Scrum na Caelum

Por Paulo Silveira em 13/09/07

O consultor e instrutor Alexandre Magno colocou suas impressões sobre o primeiro treinamento de Scrum que ele ministrou na Caelum, como segue:

Na semana de 30 de julho a 03 de agosto ministrei o treinamento “Gerenciamento de Projetos de Software com Scrum” para a primeira turma na Caelum. O resultado final superou as minhas expectativas, e um grande fator para isto foi o espírito de time que foi criado nos 05 dias que passamos juntos aprendendo Scrum. Também fiquei bastante satisfeito por ter conseguindo atingir um dos meus principais objetivos, que era o de conduzir um treinamento de Scrum focado principalmente na prática, colaborando para que todos os membros do time aprendessem com nossos próprios erros. Acho que ficou claro para todos a evolução que cada um teve no decorrer da semana através de uma comparação de resultados alcançados em cada atividade, e o quão cultural são a maioria destes problemas.

scrum caelum 1072229271_22ff49e3fd_o
1072228845_15a37026ed_o 1072228597_44c1b022c6_o

O feedback realmente foi muito bom! Tivemos uma participação bem heterogênea, desde gerentes de projeto até desenvoldores.

O Alexandre Magno estará em Londres palestrando no Scrum Gathering, com a palestra “Why Scrum Intimidates?“. Não é a toa que o Phillip Calçado, que cordena uma equipe de 8 desenvolvedores na globo.com, escreveu um excelente texto sobre como adotar agilidade na sua empresa. Nesse texto o Phillip começa falando sobre essa reação negativa a respeito de novidades, e em especial a metodologias ágeis. Além do texto, as fotos também estão excelentes. Sua empresa está pronta para a mudança?

Design Patterns no Java SE: o Template Method

Por Paulo Silveira em 04/09/07

Quando alguém aprende o que é Design Pattern, ou mesmo um novo Design Pattern, fica com aquele sentimento de que já viu isso em algum lugar antes. A API do Java SE é um excelente lugar para encontrar milhares de exemplos de Design Patterns. Só a java.util, a java.io e a java.lang já são suficientes para estudar os mais usados.

A classe abstrata java.io.InputStream é um excelente exemplo. Ela possui um conjunto de métodos para leitura de bytes, porém apenas um deles é abstrato: o método read que lê apenas um único byte. Segue seu fonte:

public abstract int read() throws IOException;

Ele é abstrato pois essa classe não sabe exatamente de onde será realizada a leitura: da entrada padrão? de um arquivo? de uma socket? Esse comportamento vai ser definido através da reescrita desse método em uma de suas subclasses concretas: FileInputStream, SocketInputStream, ByteArrayInputStream, entre outras. Essas sim sabem realizar a operação de leitura de um byte.

Se a classe InputStream não sabe ler um byte, como então é possível existir um método read que recebe um array de bytes a ser preenchido pela leitura, que não seja abstrato? Vamos ver o fonte deste método:

public int read(byte b[]) throws IOException {
   return read(b, 0, b.length);
}

Este por sua vez esta invocando o método sobrecarregado do read que recebe, além da array a ser preenchida, a posição inicial e quantos bytes devem ser lidos. O fonte deste método está abaixo:

01    public int read(byte b[]int off, int lenthrows IOException {
02       if (b == null) {
03          throw new NullPointerException();
04       else if (off < || len < || len > b.length - off) {
05          throw new IndexOutOfBoundsException();
06       else if (len == 0) {
07          return 0;
08       }
09 
10       int c = read();
11       if (c == -1) {
12          return -1;
13       }
14    
15       b[off(bytec;
16 
17       int i = 1;
18       try {
19          for (; i < len ; i++) {
20             c = read();
21             if (c == -1) {
22                break;
23             }
24             b[off + i(byte)c;
25          }
26       catch (IOException ee) {
27       }
28       return i;
29    }

Nas linhas 10 e 20 temos invocações ao método read que é abstrato! Isso é possível pois sabemos que não existe como instanciar a classe InputStream: ela é abstrata. Essa invocação recairá sobre um objeto que foi instanciado, logo ele possuirá uma implementação deste método read.

Esse é uma ótima ilustração do Template Method. Os métodos read que lêem mais de um byte são templates: eles possuem o algoritmo em si, mas ainda falta um pouco para que toda a funcionalidade deles esteja pronta. Essa parte que falta é suprida com a implementação concreta do método read nas classes filhas de InputStream. Quando a classe filha implementa esse método, os demais métodos de InputStream que dependem deste (os template methods) estarão prontos para uso!

Obviamente a OutputStream funciona de maneira análoga. No java.io ainda encontramos o Decorator Pattern, na java.awt o Composite Pattern, no java.lang temos o Builder e no java.util temos Iterator, Strategy, Prototype e muitos outros. Qual é seu exemplo preferido de Design Pattern dentro do Java SE?