DSLs não são para gerentes

Por Fabio Kung em 30/12/08

Já vi e ouvi de muitas pessoas e em muitos lugares que Domain Specific Languages são uma ótima ferramenta para deixar o código tão simples de escrever, tão legível e tão parecido com uma linguagem natural (português, inglês), que serve para que não programadores possam escrever parte do código.

A idéia é que o próprio gerente, cliente, analista-não-programador, ou alguém com este tipo de perfil não técnico escreva as regras de negócio em uma linguagem muito parecida com a linguagem natural, eliminando a necessidade de programadores e reduzindo os custos.

Este pode até ser um uso possível e interessante para DSLs, mas infelizmente, DSLs não tem a mínima pretenção de serem linguagens naturais. Isso seria muito ambicioso para o escopo de uma línguagem específica para um domínio. DSLs não foram criadas para ensinar inglês ou português aos computadores.

Existe uma outra forma interessante de usar DSLs, pela qual tenho preferência declarada e felizmente, parece haver uma convergência para esse meio de aplicá-las. O objetivo é fazer com que o código fonte do programa fique mais próximo do problema sendo resolvido. DSLs como forma de aumentar a expressividade do código. Dave Thomas e Andy Hunt tratam desse assunto no famoso livro The Pragmatic Programmer, sob o nome de Program Close to the Problem Domain.

Em seu livro Domain Driven Design, Eric Evans fala muito sobre a importância de todos os envolvidos no desenvolvimento do sistema usarem a mesma linguagem, que ele chama de linguagem ubíqua (Ubiquitous Language). Desta forma, conseguimos diminuir o abismo que existe entre programadores e especialistas no negócio, facilitando comunicação e diminuindo os clássicos problemas do “telefone sem fio”.

problemas de comunicação

problemas de comunicação

DSLs são uma ferramenta para o desenvolvedor. Uma das formas de inserir os termos da linguagem ubíqua no código fonte, de fazer o código refletir o problema que está sendo resolvido. Desta forma, programadores podem fazer com que o código fique auto explicativo e auto documentado. O próprio código se explica; é a documentação de si próprio.

Ando tão “viciado” nessa forma de escrever código, perseguindo expressividade, fazendo com que o próprio código se explique, que em muitos casos a DSL surge naturalmente depois de algumas refatorações. Aqui, Behavior Driven Development (e principalmente o ciclo red-green-refactor) tem ajudado muito, mas esse assunto fica para um próximo post.

Uma técnica simples para aumentar a expressividade é evitar escrever comentários no meio do código. O tradicional “comentário é mal cheiro no código” (code smell).

Ao invés de escrever o comentário, simplesmente extraia o código que estaria sendo comentado em um novo método. O nome deste novo método deve ser exatamente o mesmo que iria ser escrito no comentário.

“You’ve written the code, now you have to write about the code. In a perfect world, you’d never have to write comments for this purpose: the code will be expressive enough that someone who reads it will understand it. Two things help achieve this result: expressive, readable languages and the composed method pattern.” — Neal Ford

Neal Ford diz que linguagens expressivas e legíveis ajudam a eliminar a necessidade de explicar o código. Geralmente, linguagens mais modernas são mais expressivas do que as mais antigas como Assembly. Mesmo usando linguagens mais modernas, ainda podemos usar nossa ferramenta de programadores preferida: DSLs para criar a linguagem com a expressividade adequada para resolver o problema!

Essa técnica tem o seu preço, claro. É sempre uma troca, uma questão de vantagens e desvantagens; clássico trade-off. Projetar uma DSL pode dar mais trabalho no início, já que precisamos pensar em como desejamos escrever o código (Language Oriented Programming) e pode exigir alguns ciclos extras de refatoração até o código chegar num nível bom de expressividade.

O que tenho percebido é que esse possível esforço extra no desenvolvimento (nem está comprovado cientificamente que ele existe [1]), costuma compensar mais para a frente, dada a facilidade de ler e entender o código, associado ao fato dos programadores estarem naturalmente o tempo todo usando a linguagem ubíqua.

Além disso, diminui bastante a necessidade da famosa pilha de documentação extra que precisa ser escrita em muitos projetos. Na maior parte deles isso tudo é até escrito quando o software já está pronto! Não estou querendo dizer que manuais não devam ser escritos, mas boa parte desse esforço extra de documentação pode ser reduzido. Principalmente o esforço relacionado a documentação que tem como alvo outros desenvolvedores e pessoas que possivelmente darão manutenção no código.

Assim como os testes durante o desenvolvimento, podemos encarar o uso de DSLs como um investimento, e não tempo extra no desenvolvimento.

Feliz ano novo a todos!


1. eu até acho que fico mais produtivo escrevendo código desse jeito.

Testes unitários com JMock 2

Por Lucas Cavalcanti em 17/03/08

Podemos definir teste unitário de uma classe como um teste em que verificamos uma funcionalidade da classe em questão passando o mínimo possível por outras classes do sistema, ou que sejam dependências do sistema.

Por mais desacoplada que seja nossa classe, se ela tiver um mínimo de complexidade, ela vai precisar de funcionalidades de outras classe, ou seja, ela vai ter dependências. E essas dependências sempre nos atrapalham na hora de fazermos os testes unitários da classe.

Por exemplo, se uma das dependências da nossa classe é a interface HttpServletRequest, o que vamos passar pra ela na hora do teste? null, e levar uma NullPointerException? Desenterrar a biblioteca e passar uma implementação de verdade? Claro que não.

Em testes unitários não estamos interessados no comportamento real das dependências da classe, mas em como a classe em questão se comporta diante das possíveis respostas das dependências, ou então se a classe modificou as dependências da maneira esperada.

Então é comum passarmos implementações falsas (mock objects) das dependências da classe, retornando valores pertinentes para conseguirmos testá-la satisfatoriamente. Esses mocks costumam poluir seu código de testes com várias classes que só são usadas em poucos testes. E o problema fica pior quando a interface da dependência possui muitos métodos mas só usamos um ou dois deles (é o caso do HttpServletRequest). É nessa hora que o JMock vem para facilitar a nossa vida.

O JMock é uma biblioteca que auxilia o Test Driven Development através dos mock objects. É uma biblioteca que vai criar implementações de mentira específicas para o seu teste, de uma maneira rápida e simples, sem ter que se preocupar com os métodos que não vamos usar no teste, sem ao menos ficar criando classes “a toa”. Com o JMock podemos definir o comportamento necessário do objeto de mentira, para criarmos a situação pedida pelo teste.

Para termos uma idéia melhor vamos ver um exemplo prático de como usar o JMock. Vamos supor que temos uma lógica de login no nosso sistema e queremos testá-la. As dependências dessa lógica são recebidas no construtor:

public class LoginLogic {
    private final HttpSession session;
    private final Authenticator auth;
    public LoginLogic (HttpSession session, Authenticator auth) {
        this
.session = session;
        this.auth = auth;
    }
    public void login(User user) {
        //…
    }
}

Vamos testar o método login, que tem duas situações possíveis: se o usuário é válido, entra na sessão, senão não entra. Para testar precisamos de um teste que vai passar um usuário válido, e garantir que o usuário entrou na sessão; e um teste que vai passar um usuário inválido e garantir que o usuário não entrou na sessão.

Para criar esses testes precisamos de uma instância da classe LoginLogic, logo precisamos nos preocupar com as dependências da classe: o HttpSession, que é uma interface do java servlet, e o Authenticator, que é uma interface do nosso projeto contendo um método chamado isValid(User) que recebe um usuário e vê se ele é válido.

Começando nosso teste:

LoginLogic logic = new LoginLogic(/*um httpSession*/, /*um authenticator*/);
logic.login(/*um user válido*/);
assertTrue(/*o usuário está na sessão*/);

Precisamos passar para nossa lógica um objeto que implementa HttpSession, e um que implementa Authenticator. Poderíamos criar implementações falsas dessas interfaces, mas não queremos poluir nosso código de testes com classes “inúteis”. Vamos, então, deixar o JMock fazer esse trabalho sujo para nós.

Para começar a criar essas implementações falsas, precisamos de uma fábrica de objetos falsos (mocks) do JMock:

Mockery mockery = new Mockery();

É comum criarmos essa fábrica no começo de cada teste, ou no setUp da sua classe de testes (se você usa o JUnit). Com a fábrica em mãos, podemos começar a mockar as nossas interfaces. É bem simples:

HttpSession session = mockery.mock(HttpSession.class);
Authenticator auth = mockery.mock(Authenticator.class);

E já podemos passar esses mocks para nosso LoginLogic. Nosso teste ficaria então:

final Mockery mockery = new Mockery();
final HttpSession session = mockery.mock(HttpSession.class);
final Authenticator auth = mockery.mock(Authenticator.class);

LoginLogic logic = new LoginLogic(session, auth);
logic.login(/*um user válido*/);
assertTrue(/*o usuário está na sessão*/);

Se fizermos simplesmente isso, o teste não vai fazer nada. Precisamos dizer para nossos mocks como vai ser o comportamento deles quando forem acessados. A maneira de fazer isso no JMock 2.4 é bem interessante, utiliza uma sintaxe um pouco incomum, mas quando você se acostuma com ela fica bastante legível:

mockery.checking(new Expectations() {{
    //comportamento dos mocks aqui
}});

Repare no truque: uma classe anônima, com um pré construtor (note os dois grupos de chaves). Esse bloco vai ser chamado pelo método checking, e vai adicionar o comportamento pedido aos mocks. Fazemos isso de uma maneira fluente bem interessante. Por exemplo, se quisermos garantir que o usuário foi colocado na sessão após o teste, fazemos:

one(session).setAttribute(“user”, with(any(User.class)));

O que essa linha quer dizer é o seguinte: eu quero que o método setAttribute seja chamado uma vez, com os parâmetros “user” e com qualquer objeto do tipo User.

Se esse método nunca for chamado com esses parâmetros ou for chamado mais de uma vez, o teste vai falhar. Na verdade, tudo que é possível acontecer com os mocks tem que estar descrito em um expectations (pode ter mais de um no teste). Se pro teste não é importante o que acontece com um dos objetos mockados, podemos ignorá-los, escrevendo:

ignoring(mockObject);

Fazendo isso, todo método chamado desse método vai retornar valores padrão (0, “”, null ou, se for possível um outro mockObject marcado como ignoring). Mas para que tudo isso aconteça, precisamos colocar no final do nosso teste a seguinte linha:

mockery.assertIsSatisfied();

Bom, ainda não terminamos o nosso teste, precisamos fazer com que o usuário seja válido, ou seja, precisamos garantir que o método isValid() do authenticator retorne true. Fazemos isso da seguinte maneira, dentro do expectations:

one(auth).isValid(with(any(User.class)));
will(returnValue(true));

Fica bastante legível: o método isValid do mock auth será chamado uma vez, com qualquer User como parâmetro, e vai retornar o valor true.

O nosso teste completo ficaria, então:

final Mockery mockery = new Mockery();
final HttpSession session = mockery.mock(HttpSession.class);
final Authenticator auth = mockery.mock(Authenticator.class)

mockery.checking(new Expectations() {{
    one(session).setAttribute(“user”, with(any(User.class)));
    one(auth).isValid(with(any(User.class)));
    will(returnValue(true));
}});

LoginLogic logic = new LoginLogic(session, auth);
logic.login(new User());

mockery.assertIsSatisfied();

Note que poderíamos garantir que o user passado para os métodos foi o mesmo que passamos para a lógica, apenas colocando ele como parâmetro para o método. E para fazer o teste do usuário inválido só precisamos mudar o conteúdo do expectations, substituindo por:

never(session).setAttribute(“user”, with(any(User.class)));
one(auth).isValid(with(any(User.class)));
will(returnValue(false));

Ou seja, o isValid vai retornar false, e o método setAttibute nunca vai ser chamado. Se isso acontecer, o teste passa.

O roteiro do expectations não precisa estar na ordem em que ele vai acontecer no seu método (por exemplo o isValid provavelmente vai ser chamado antes do setAttribute), mas tem um jeito de você garantir a ordem dos comandos.

Por padrão o JMock só é capaz de mockar interfaces, mas você pode configurá-lo para mockar classes.

Você pode, ainda, usar o JMock para fazer testes de integração. Como o roteiro do teste pode ficar bastante complexo, você pode utilizar uma máquina de estados para adicionar um pouco mais de poder ao roteiro (por exemplo garantir que uma chamada de método sempre ocorra depois de uma outra).

Uma chamada genérica num expectations, então, seria:

invocation-count (mock-object).method(argument-constraints);
inSequence(sequence-name);
when(state-machine.is(state-name));
will(action);
then(state-machine.is(new-state-name));

Se você quiser usar todo o poder do JMock, dê uma olhada na sua documentação. E, claro, não abuse de mocks colocando-os em todas as situações, senão seu teste unitário pode perder o valor de testar uma pequena unidade.

JustJava 2007, Arquitetura e Caelum

Por Paulo Silveira em 08/10/07

Neste último JustJava palestrei juntamente com o Phillip Calçado a respeito das novidades em relação a arquitetura Java. Aproveitei para extrair conhecimento e idéias do Phillip, que sempre anda muito ligado com as novidades.

DSC00334 DSC00359

Como a apresentação é bem sucinta, vale falar um pouco sobre ela. O intuito foi mostrar as arquiteturas e designs enlatados e que durante muito tempo reinaram por aí, seja na forma de Core J2EE patterns mal aplicados ou no mal uso de clusters. O início da palestra mostra diversos pontos do nosso cotidiano de décadas passadas: destaque para os design patterns ValueObject (na realidade TransferObject), BusinessDelegate e ServiceLocator. Esses design patterns faziam muito sentido quando no J2EE os entity beans não eram serializáveis, e sim acessados remotamente, além de que não existia injeção de dependência.

Apesar do Java EE 5.0 trazer essas novidades, muita gente acaba aplicando esses design patterns sem nenhuma necessidade, o que acaba criando o padrão carinhosamente chamado de BOLOVO pelo Phillip: classes como UsuarioBusinessObject (BO), UsuarioLayerObject (LO), UsuarioValueObject (VO), UsuarioXYZ, etc.

Toda vez que uma classe de domínio é criada em um sistema como esses, suas irmãs também aparecem: BOs, VOs, LOs, XYZs. Utilizar TOs apenas para transportar objetos entre camadas (e não tiers) não faz sentido algum, polui o código e diminui flexibilidade e manutenção. Separar funcionalidade e dados entre BOs e VOs é outro grande problema: onde está a orientação a objetos? Entity beans apenas com getters e setters é um mau sinal.

Passamos por Model Driven Design, dando alguns exemplos com código de Domain Driven Design e de Domain Specific Languages. Por último atacamos SOA, comparando o modelo WSDL/SOAP com o Plain Old XML (POX), além de outras alternativas. Cada forma de webservices, seja SOAP, POX ou JSON, tem seu caso de uso. O que mostramos na palestra foi que não deve-se optar diretamente por SOAP sem antes pensar bem nas necessidades para aquele serviço. O mesmo para os design patterns, práticas e idéias aqui discutidos ou até mesmo criticados: cada um tem seu lugar, não utilize-os apenas porque estão em um livro ou já foram muito usados em prévias arquiteturas.

No final falamos que não há uma arquitetura enlatada que possa resolver todos nossos problemas, e que devemos tomar muito cuidado para não construir um monstro para resolver um pequeno sistema. Cada projeto deve ter sua arquitetura muito bem estudada, com todas as suas particularidades.

Na sexta feira ainda tivemos uma palestra de Lucene apresentada pelo Guilherme Moreira. Aproveitando a ocasião, este domingo ocorreu uma confraternização e reunião da Caelum para discutir idéias, projetos e metas.

DSC00377 DSC00389
Time Caelum Out/2007 faltando 3 pessoas DSC00392

Tivemos novos participantes, os mais novos integrantes e colaboradores da Caelum: Jonas Abreu, Cecília Fernandes, Alexandre Magno, Danilo Sato e Ricardo Nakamura! O time está crescendo. Nas fotos faltam apenas o Carlos Felício, Suellen Campana e o Rafael Consentino.

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.