Por que eu devo ler este artigo:Os paradigmas de programação orientada a aspectos e de programação funcional já demonstraram ser a evolução no que diz respeito à estruturação de softwares modernos; ambos se apresentam como extensões do raciocínio orientado a objetos e se diferenciam nas suas utilidades, objetivos, benefícios e fraquezas. Devido à diferença de objetivos, em alguns casos, a coesão proporcionada pelos aspectos pode conduzir a problemas de side-effects. Então, como mesclar os dois modelos e tirar proveito do que há de melhor nos dois mundos?

Neste artigo entenderemos as principais características destes dois paradigmas, bem como suas principais diferenças e como utilizá-los em conjunto através de abstrações Monads, que são recursos já bem consolidadas no mundo funcional.

A partir do Java 8, o desenvolvedor ganhou novas ferramentas para trabalhar de forma diferente. Agora é possível desenvolver códigos orientados à programação funcional de forma mais fácil e tirar proveito do que há de melhor neste estilo: concisão, estética, paralelismo e facilidade de manutenção. Porém, não podemos esquecer que o desenvolvimento em Java pode (e deve) se beneficiar do fato de a linguagem não ser puramente funcional. A vantagem desta característica é a possibilidade de unir à programação orientada a objetos duas formas de pensar na criação de softwares: a programação funcional e a programação orientada a aspectos. Como veremos ao longo deste artigo, cada paradigma se apresenta como melhor solução dependendo da estrutura que se pretenda construir em função do modelo e da escalabilidade.

Hoje o desenvolvedor Java tem em mãos uma linguagem que permite, ao mesmo tempo, escrever códigos que se favoreçam tanto da elegância dos aspectos como forma de separar interesses ortogonais ao domínio da regra de negócio, quanto da eficiência da programação funcional, com a sua imposição como um estilo mais sucinto e, ao mesmo, tempo eficaz. Entretanto, deve-se ter cautela ao mesclar estas duas técnicas no mesmo código, pois apesar de terem objetivos diferentes, a utilização de aspectos pode introduzir efeitos colaterais, ou melhor, side-effects (ver BOX 1) ao código, eliminando um dos maiores benefícios da programação funcional, que é exatamente a eliminação destes efeitos, para que o código torne-se mais facilmente modificável, paralelizável e livre de bugs.

Além disso, a utilização indevida de aspectos em códigos funcionais pode quebrar um dos preceitos fundamentais da programação funcional, que consiste em funções que retornem sempre o mesmo resultado, uma vez utilizados os mesmos parâmetros independentemente de quantas vezes a função seja executada. Este problema pode ocorrer por causa dos pointcuts (pontos de corte) necessários ao processo de interceptação do código no fluxo principal para que outro algoritmo de interesse transversal seja executado. Neste caso, o código transversal pode alterar o valor de parâmetros ou do próprio retorno da função, criando toda a problemática aqui citada. Por outro lado, a não utilização dos aspectos pode resultar em códigos de difícil manutenção e com classes pouco coesivas, o que dificulta o reaproveitamento em outros módulos ou sistemas.

Neste artigo vamos revisitar os conceitos por trás da orientação a aspectos e compará-los com aqueles da programação funcional. Veremos como utilizar ambos os paradigmas para construir um programa ao mesmo tempo livre de side-effects e bem modularizado.

BOX 1. Side Effect

Chamam-se Side Effects as características de uma função que além de prover um valor de retorno, também modifica o estado de algum atributo ou efetua alguma interação com um ambiente externo (um sistema de arquivos, por exemplo). Esta é uma característica muito comum em linguagens de programação imperativas devido à possibilidade de definir variáveis com escopos globais em relação ao método a ser executado.

Estes efeitos são fortemente criticados e evitados em linguagens de programação que pretendem ser funcionais devido às suas contradições com os preceitos deste paradigma, que prevê funções cujas execuções devem manter a consistência de resultados em função dos parâmetros informados. Neste caso, uma função que efetue operações em escopos externos como variáveis globais, sistemas de arquivos, acesso a serviços, entre outros, se tornará vulnerável a efeitos que não dependerão exclusivamente das variáveis passadas como parâmetros ou definidas em seu escopo, quebrando, portanto, a coerência dos resultados.

Além da coerência das funções, a inexistência de Side Effects em um programa garantirá o benefício de Thread Safety, que possibilitará a paralelização de qualquer ponto da aplicação com pouco ou nenhum esforço, uma vez que sem tais efeitos a concorrência de threads por algum recurso externo será inexistente.

Falando de Objetos

Para que possamos compreender a necessidade da semântica de aspectos sobre o raciocínio orientado a objetos, faz-se necessário revisitarmos os conceitos fundamentais da modelagem orientada a objetos. Suas premissas podem ser resumidas em quatro proposições básicas:

1. Um objeto não deve ter acesso a informações que não façam parte do seu escopo;

2. Todas as mudanças de estado em um objeto devem ocorrer somente através da sua interface;

3. As relações de herança entre as classes devem manter a coerência conceitual;

4. O entrelaçamento entre classes deve ser o menor possível.

Estes preceitos apresentam limitações ao tratar comportamentos que se espalham pelos objetos quando existem outros interesses além da própria regra de negócio. O exemplo mais clássico sobre esta falha é o da auditoria através de log. Esse requisito está associado ao registro das atividades executadas no software para análises posteriores sobre erros, fraudes, tendências de execução, mineração de dados, entre outros, atravessando toda a regra de negócios de forma sincronizada com a execução para que se obtenha a fidelidade e coerência do histórico. Para exemplificar a falha de modelagem no que diz respeito a interesses transversais entre a regra de negócio e a atividade de log, na Listagem 1 declaramos uma classe CarrinhoDeCompra que possui um método simples de adicionar itens e outro para a configuração de um objeto que será utilizado para o registro de log.

Listagem 1. Declaração da classe CarrinhoDeCompra.


  1.  package br.com.jm.faop.business;
  2.  
  3.  import java.util.ArrayList;
  4.  import java.util.List;
  5.  import java.util.logging.Logger;
  6.  import br.com.jm.faop.business.Item;
  7.  
  8.  public class CarrinhoDeCompra {
  9.   
  10.  private List<Item> items = new ArrayList<>();
  11.  private Logger logger;
  12.  
  13.  public void addItem(Item item) {
  14.  this.items.add(item);
  15.  this.logger.info(String.format("Item [%s] adicionado ao carrinho de compra.", item.getName()));
  16.  }
  17.  
  18.  public void setLogger(Logger logger) {
  19.  this.logger = logger;
  20.  } 
  21. 
  22.  public List<Item> getItems() {
  23.  return items;
  24.  }
  25. }

Listagem 2. Declaração da classe Item.


  1.  package br.com.jm.faop.business;
  2.  
  3.  public class Item {
  4.   private String name;
  5.  
  6.   public Item(String name) {
  7.   this.name = name;
  8.   }
  9.   
  10.  public Str ... 

Quer ler esse conteúdo completo? Tenha acesso completo