Código expressivo e programação funcional em Java com LambdaJ

Há um certo tempo refatoramos o sistema que trabalhamos. Migramos de uma arquitetura definida pelo Fowler como Transaction Script (explicada nesse edição da MundoJ) para uma arquitetura essencialmente O.O Domain Model. Por incrível que pareça, o que mais nos chamou a atenção nesse processo não foi o fato de transferir a lógica de negócios para as entidades responsáveis, visto que delegar responsabilidades é complicado e é isso que faz do O.O tão desafiante, mas sim a qualidade na expressividade do código que atingimos.

Alguns sinais de que seu código não esteja expressivo são os comentários. A lógica é que, se você está explicando com comentários algo que o próprio código deveria dizer, é um smell, neste caso um forte sinal da falta de expressividade (não para a linguagem, mas não está se expressando bem para outro programador).

Sabemos que programador odeia ver código de outro programador, e nos tempos atuais onde a agilidade e ideia de código coletivo estão cada vez mais em alta, código expressivo passa a ser uma exigência, quase que um requisito não funcional. Vamos considerar o código abaixo, tente lê-lo você mesmo:

public void imprimirEstatisticasDeVenda(Vendedor vendedor) {
	BigDecimal total = BigDecimal.ZERO;
	BigDecimal comissao = BigDecimal.ZERO;
	for (Venda venda : vendedor.getVendas()) {
		total = total.add(venda.getQuantidade().multiply(venda.getPreco()));
		if(venda.getQuantidade().doubleValue() >= 5) {
			if(venda.getPreco().doubleValue() >= 5) {
				comissao = comissao.add(venda.getQuantidade().
					multiply(venda.getPreco()).multiply(new BigDecimal(0.05)));
				venda.priorizarDespache();
			}
		}
	}
	System.out.println(vendedor.getNome() + " - Total arrecadado: "
		+ df.format(total) + " - Comissao: " + df.format(comissao));
}

Se analisarmos linha a linha, veremos que este código calcula o total arrecadado a partir das vendas, o total de comissão que deve ser pago ao vendedor que efetuou as vendas, e finalmente prioriza aquelas vendas que tiveram quantidade e preço maior que cinco.

Ele poderia ser melhorado? Sim, de diversas formas: poderíamos ter extraído varias partes desse código para métodos menores e com nomes significativos para torná-lo mais limpo. Outra opção é buscar por bibliotecas ou criar suas próprias DSL’s.

Para não esperar as closures do Java 8, conseguimos um pouco de programação funcional utilizando a biblioteca LambdaJ, dando um toque de cálculo de lambda ao Java. Ao reesrever o código utilizando essa biblioteca, temos algo mais enxuto:

public void imprimirEstatisticasDeVenda(Vendedor vendedor) {
	List<Venda> vendas = vendedor.getVendas();
	BigDecimal totalArrecadado = sumFrom(vendas).getValorDaVenda();

	List<Venda> vendasComissionaveis = select(vendas,
		comQuantidadeMaiorOuIgualA(5).and(comPrecoMaiorOuIgualA(5)));
	forEach(vendasComissionaveis).priorizarDespache();

	BigDecimal comissao = sumFrom(vendasComissionaveis)
	        .getValorDaVenda().multiply(new BigDecimal(0.05));

	System.out.println(vendedor.getNome() + " - Total arrecadado: "
		+ df.format(totalArrecadado) + " - Comissao: " + df.format(comissao));
}

Seria esse um código mais expressivo? Consideramos que sim, mas alguns vão criticar que estamos escondendo uma certa complexidade do sistema, que deveria estar a vista dos programadores. E a performance? O LambdaJ tem suas inicializações e objetos temporários, sendo um pouco mais lento que a versão anterior, podendo trazer problemas em laços. Há também o problema de ser uma biblioteca de fora da API padrão, e os desenvolvedores precisam de um tempo, mesmo que curto, para se adaptar às novas ideias trazidas por ela. Se você já cursou o FJ-16, pode conferir a modelagem em Scala, mais funcional. 

Expressividade de código derruba alguns conceitos que aprendemos, principalmente sobre Design Patterns. Afinal, o que o seu cliente entende: Usar um BO para alterar o VO e persistir usando o DAO no MYSQL ou “salvar cliente  no repositório de cadastros“?

29 Comentários

  1. Caio Ribeiro Pereira 13/02/2012 at 13:56 #

    Não conhecia esse LambdaJ, parece que ele melhora a expressividade do Jaba muito bem, mas e sobre performance será que não vale pena esperar pelo Java 8 para obter Closures?

  2. Paulo Silveira 13/02/2012 at 14:13 #

    Essa é uma questão importante. Lambda no java 8 só sai em julho de 2013… esperar até lá é esperar muito. Ao mesmo tempo, ter de refatorar tudo isso depois, dá um trabalho, mas eu encararia. Você é que tem de pesar.

  3. Raphael Lacerda 13/02/2012 at 14:16 #

    Cara, em minha opinião, poder escrever essa linha de codigo aqui

    List vendasComissionaveis = select(vendas,
    comQuantidadeMaiorOuIgualA(5).and(comPrecoMaiorOuIgualA(5)));

    vale qualquer refactoring!

    =)

  4. Raphael Lacerda 13/02/2012 at 14:28 #

    Aqui estão as motivações que leveram o Mario Fusco a criar o LambdaJ

    http://code.google.com/p/lambdaj/wiki/WhyLambdajIsBorn

    “.. Developers spent more time trying to figure out what a given loop did than to write the loop itself….”

  5. Alexandre Gama 13/02/2012 at 14:39 #

    Pois é Raphael, vale a pena mesmo! =)

    Gostei bastante da API e vou fazer alguns testes de performance, mas creio que só terei algo visível com muitas iterações ou algo assim né.

    Já brinquei com algo assim mas criando na mão, com um Builder por exemplo mas só pra coisas bem simples e mesmo assim só uso Builder nos testes =P

    Valeu Raphael e Geraldo! =)

  6. Geraldo Ferraz 13/02/2012 at 15:07 #

    Alexandre, fiz alguns testes com os exemplos do post e o resultado não foi diferente daquele no link.

    Para o modelo iterativo em media: 7ms
    Para o modelo funcional com LambdaJ em media: 650ms

    Para o usuário final mudar de 7ms para 650ms é quase imperceptível.

    Agora 7ms para 650ms por Request pode começar a fazer diferença para o servidor.

    Tradeoff

  7. Alexandre Gama 13/02/2012 at 15:30 #

    Ah sim, por Request começa a incomodar mesmo.

    Valeu Geraldo!

  8. Caio Ribeiro Pereira 13/02/2012 at 17:00 #

    Interessante essas análises Geraldo Ferraz, praticamente se fizer um refactoring em um projeto web grande utilizando LambdaJ baseado nesses resultados, não teria boa performance quanto o closures do Java 8 que será nativo.

    Mas é como todos disseram, é tudo questão de ponderar performance e a qualidade do código.

  9. Caio Ribeiro Pereira 13/02/2012 at 17:01 #

    Mas o projeto LambdaJ é uma excelente lib pra começar novos projetos já implementando essas boas práticas no Java

  10. diego 14/02/2012 at 08:22 #

    vcs tem injetado o repositório nas entidades?
    parabens pelo artigo

  11. Seabra 14/02/2012 at 14:04 #

    Muito interessante. Com certeza vai entrar pro meu backlog de leituras que pretendo fazer 🙂

  12. Raphael Lacerda 14/02/2012 at 14:10 #

    Diego! Pra esse projeto específico não… Mas em outros experimentei sim e gostei bastante!
    No post sobre Repository, aconselho a leitura dos comentários! São bem instigantes!

  13. diego 14/02/2012 at 14:33 #

    Eu li o post, achei extremamente interessante.

    Mas sobre as operações de crud. Fica direto no domain, e recebe o mesmo domain por parâmetro?
    Por que com AR fica meio estranho. (to falando de Java)

    Muito fera a ideia de compartilhar as experiencias de vocês
    😉

  14. Geraldo Ferraz 14/02/2012 at 14:50 #

    Diego,
    Acredito que para operações de crud, seguindo o modelo Active Record, o domínio não receberia ele mesmo e sim, salvaria a si mesmo. Algo assim:
    Venda v = new Venda(repositorio/dao); //exemplo
    venda.setValor(10.0);
    venda.setQuantidade(5);
    venda.save();
    venda.setValor(8.0);
    venda.update();
    venda.delete();

  15. diego 14/02/2012 at 15:19 #

    Isso. Mas minha ideia seria não usar AR no Java.

    Talvez minha pergunta não compreensível, vou reformular.

    Se injetar o repositório no domínio, como fica as operações de crud?

    valeu @geraldo

  16. Gerson K. Motoyama 15/02/2012 at 09:55 #

    Olá Raphael!

    Mesmo sem ter o conhecimento completo do domínio, tenho algumas observações sobre o código.
    – Violação do SRP (calcula total arrecadado, prioriza o despache, calcula a comissão e ainda imprime…).
    – Continua sendo um código altamente procedural, principalmente se esse método faz parte de um domain service / application service (DDD), ou algo parecido. Algumas abstrações adicionais (representadas por novas classes que não sejam ‘services’) melhorariam o design.
    – Provavelmente está misturando níveis de abstrações no mesmo método. Melhorar esse “detalhe” (aplicando o ‘One Level of Abstraction per Function’ – Clean Code) costuma facilitar bastante a leitura de um método.
    – Combinar lambdaj e extract method é uma opção também.
    – Poderia explicar melhor sobre a sua afirmação em “expressividade de código derruba alguns conceitos, principalmente sobre Design Patterns”?

  17. Raphael Lacerda 15/02/2012 at 10:21 #

    Opa @Gerson.. tudo bom?

    O código foi feito de uma forma bem didática para tentar mostrar algumas opções do LambdaJ sem tem que fazer um POST muito grande. Ou seja, é um trade-off, tentar passar o máximo de informação em uma quantidade resumida de linhas!
    Por isso essa licença poética de ignorar alguns conceitos como SRP..
    Todavia, ótima observação, com certeza deveria ser extraído a responsabilidade para outros métodos… =)

    A ideia principal é mostrar o poder que uma linha como a descrita abaixo tem em relação à expressividade de código. Onde vc irá utilizá-la depende de como está a modelagem do seu sistema (Mais O.O, Transaction, Facade, Service, etc…)
    List vendasComissionaveis = select(vendas, comQuantidadeMaiorOuIgualA(5).and(comPrecoMaiorOuIgualA(5)));

    Combinamos LambdaJ com extract method no método comQuantidadeMaiorOuIgualA(5)… na verdade o LambdaJ recebe o método having. Basicamente para ter mais expressividade!

    Por fim, a explicação para essa afirmação está no questionamento posterior, que agora peço a sua opinião…
    “Afinal, o que o seu cliente entende: Usar um BO para alterar o VO e persistir usando o DAO no MYSQL ou “salvar cliente no repositório de cadastros“?

  18. Paulo Silveira 15/02/2012 at 10:59 #

    Realmente, quebrar em vários métodos seria ai essencial, mas acho que Raphael e Geraldo escreveram exatamente isso no post, que não quebraram justo para ver onde ia dar.

    Só lembrando que SRP é referente ao objeto, e não a um único método. Então depende qual é a responsabiidade da classe.

  19. Daniel 15/02/2012 at 11:20 #

    Acho que vou ser o chato aqui, mas mesmo gostando muito da expressividade de lambda nao creio que ela seja a soluçao dos problemas considerando que ”codigo mal escrito, vai ser mal escrito em qualquer lugar”.
    Com certeza a utilização do lambdaJ facilita bastante, mas acho que praticas e conceitos de O.O sempre foram mal aplicadas na linguagem Java, e que existem mais programadores desenvolvendo proceduralmente em Java do que orientado a objetos. E isso fica ainda mais claro quando pegamos projetos de webservices ou ejbs por ai.

  20. Raphael Lacerda 15/02/2012 at 11:35 #

    @Daniel, acho que vc não foi chato! Eu pelo o menos concordo contigo!

    Tem um post bem bacana do Guilherme Silveira que abusa de boas praticas de O.O como polimorfismo em detrimento de if’s
    blog.caelum.com.br/como-nao-aprender-orientacao-a-objetos-o-excesso-de- ifs/

  21. Gerson K. Motoyama 15/02/2012 at 12:34 #

    Paulo,

    A responsabilidade de uma classe, no contexto do SRP, é definida por ‘only one reason to change’. A não ser que todas ‘possíveis resposabilidades’ que eu listei sempre alteram juntas (o que acho muito improvável), a classe viola sim o SRP (mesmo sem saber a responsabilidade atribuída a classe – afinal de contas uma classe chamada ‘FazIssoFazAquiloEMaisUmMonteDeCoisas’ continua violando o SRP, não importa o que encontrar dentro dela).

    Sim, ficar extraindo em métodos e mais métodos não elimina a violação do SRP, muito menos deixa o código “mais OO”.

  22. Raphael Lacerda 15/02/2012 at 12:46 #

    “Sim, ficar extraindo em métodos e mais métodos não elimina a violação do SRP, muito menos deixa o código “mais OO”.

    Sim concordo, mais uma vez, licença poética por um bem maior para demonstrar expressividade de código. O correto seria colocar esses métodos em classes separadas seguindo o princípio mencionado! =)

  23. Paulo Silveira 15/02/2012 at 12:54 #

    Tem razão Gerson, o FazIssoFazAquilo precisa ser quebrado em dois. Também ficaria bastante expressivo, independente de usar o lambda internamente para deixar as regras de negócio da cada um expressivo. Também não conheço o domínio, mas o cara que fosse calcular a comissão utilizaria a composição de cada um. Chutando:

    CriterioDeSelecao criterio = new CriterioDeSelecao (5, 5); // 5 dias e 5 reais
    double comissao =  new CalculadorComissao(criterioDeSelecao, new SumarizadorDeTotalArrecadado(vendas).getTotalComissao();
    

    Ou ainda colocando alguns metodos/responsabilidades dentro do model, como dentro de Vendas, se fosse o caso (em vez de utilizar Collection)

    Obviamente, levando em cosideração o que você já falou: nesse caso eles trabalhariam de forma independentes (muda-um nao implica em muda-outro).

  24. Rafael Steil 22/02/2012 at 22:26 #

    Eu fico com o pé bem atrás quando vejo coisas desse tipo aplicadas em um contexto que não seja natural para este tipo de abordagem. Aliás, para mim a compreensão de *como* fazer as coisas é mais facilmente prejudicada do que se fosse feito da maneira “normal”.

    Pouquíssimas pessoas sabem usar Fluent Interfaces, e quando usam costuma ser num contexto mais isolado, e eu sou um tanto incrédulo de que tal forma de escrever o código representa um ganho expressivo frente a uma boa separação de responsabilidades e bom conjunto de nomenclaturas (que é algo bem mais fácil de conseguir).

    Por exemplo, um código como

    result.use(page()).of(this.getClass()).list();

    é difícil de compreender, e se a documentação não for ótima (quando é?), descobrir *o que* invocar e *quais* as outras possibilidades torna-se uma cruzada muitas vezes frustrante.

  25. Alberto Souza 23/02/2012 at 00:29 #

    Parabéns, excelente post!

    Uma coisa que não entendi @Gerson , você comentou das práticas e tal… O que tem a ver o lambdaj com a quebra das práticas? Quebrar boa prática de O.O é o que vemos aí todos os dias… Mesmo o código continuando procedural, não que essa seja a discussão proposta aqui, acho que uma lib dessa facilita o entendimento do código que trabalha bastante com collections. Pode ser apenas o ponto inicial de uma possível refatoração. Colocar esses códigos nos seus devidos lugares é outra conversa.

  26. André Thiago 26/02/2012 at 22:11 #

    Gostei demais!!!!

    Vou estudar mais sobre isso e com certeza passar a usar em meus projetos!

  27. Renato Maia 04/04/2012 at 15:52 #

    Ótimo post! Com certeza o código fica bem mais expressivo!…

  28. loko abreudede 15/09/2012 at 16:24 #

    vcs explicao muito bem

  29. Pedro Lira 20/01/2015 at 16:43 #

    Ótimo post, ainda não tenho tanto conhecimento sobre o lambda, mas estou começando a entender a utilidade.

Deixe uma resposta