Uma das melhores e mais modernas linguagens de programação imperativa e orientada a objetos, Java suporta a maior parte do desenvolvimento de software atual. Mas existem muitas tarefas que se beneficiam de linguagens especializadas.

Por exemplo, para acessar bancos de dados (SGBDs) relacionais você normalmente usa SQL. Como sabemos, o SQL não é uma “linguagem primária” da plataforma Java: nenhum compilador ou interpretador de SQL faz parte da JVM. Além disso, código SQL não é compilado diretamente para bytecode (o que não faria muito sentido, pois a JVM não integra um SGBD). Ainda assim, o SQL é suportado de forma “secundária” através da API JDBC e seus drivers.

Outra aplicação de linguagens especializadas é na programação baseada em regras. Um exemplo é a linguagem Prolog, que muitos aprendem na universidade, em disciplinas de Lógica ou Inteligência Artificial (IA). Todas as técnicas de IA podem ser implementadas numa linguagem convencional, como Java, usando bibliotecas/APIs comuns, em que todas as tarefas são conduzidas através da criação de objetos e invocação de métodos da API. Mas em alguns casos isso seria parecido com alguns mecanismos de persistência que não suportam SQL. Eles resolvem problemas simples, mas você logo sente falta do SQL quando precisa de consultas complexas ou quer migrar para outro produto. Assim como no caso do SQL, a programação baseada em regras define um novo paradigma de programação, e exige linguagens próprias para maximizar a facilidade de desenvolvimento, o desempenho ou a interoperabilidade.

Existem implementações de linguagens de IA tradicionais para máquinas virtuais Java. Só de Prolog contei 14 – incluindo interpretadores escritos em Java e compiladores que geram bytecode. Produtos desse tipo são interessantes para quem já está habituado às outras linguagens, ou precisa lidar com código legado, mas em geral haverá redundância de funcionalidades e dificuldades quanto à portabilidade.

A JSR-94 e engines de regras

Uma solução para esses problemas veio com a JSR-94 (Java Rule Engine API). A JSR-94 padroniza a API de um engine de regras, como é chamado o coração de um sistema de processamento de regras (você também encontrará a expressão mais pomposa “motor de inferência”, significando a mesma coisa).

Muitos engines de regras, tanto proprietários quanto livres, foram implementados para a plataforma Java, antes de qualquer padronização. A JSR-94 foi aprovada apenas no final de 2003, e os produtos ainda estão correndo atrás da compatibilidade. Para este artigo escolhi o JESS, que é a implementação de referência (RI) da JSR-94.

Um engine de regras trabalha com dois conceitos principais. O primeiro é a base de conhecimento, organizada como um conjunto de fatos (afirmações que conhecemos ser verdadeiras, como “Hoje faz sol” ou “nome = ‘Osvaldo’”). O segundo conceito é um conjunto de regras (ruleset) com uma estrutura "se-então", por exempl "se hoje está fazendo sol, então vou à praia”. O funcionamento do engine consiste em cruzar as regras com os fatos aos quais se aplicam, gerando novos fatos, ou ações arbitrárias.

Cada engine pode suportar linguagens diferentes para a especificação das regras e da base de conhecimento. Voltando à comparação, é como se a API JDBC não exigisse que todos os bancos de dados suportassem SQL: todos os drivers teriam que implementar métodos como executeQuery(consulta), mas a linguagem usada no parâmetro iria variar de um SGBD para outro. Se você pensar bem, é isso o que acontece. Apesar da existência dos padrões ANSI, não há dois bancos de dados que implementem exatamente o mesmo SQL, sendo possível utilizar, mesmo via JDBC, um enorme número de sintaxes proprietárias. A JSR-94 é um pouco parecida com isso. Ainda que cada engine possa usar uma linguagem diferente, essas linguagens têm o mesmo propósito e a mesma funcionalidade básica – muitas diferenças não passam de detalhes sintáticos.

A utilidade de regras

Em primeiro lugar, vamos à pergunta essencial: “para que serve isto?”. O estilo imperativo de programação é muito bom quando temos algoritmos relativamente simples na tomada de decisões: "se o saldo é negativo, debite os juros"; "se o botão Ok foi pressionado, grave as alterações" etc.

Um sistema de informação típico pode ser visto como uma grande coleção de regras do tipo “se isto for verdade, então faça aquilo”. Decisões mais complexas costumam resultar em métodos cheios de estruturas de controle, variáveis temporárias, e invocações a outros métodos, nos quais são encapsuladas as decisões comuns ou recorrentes. Mas, ainda nos piores casos, a complexidade dessas decisões é relativamente pequena, podendo ser representada de forma simples, seja numa linguagem de programação como Java, seja numa linguagem de modelagem como UML (por exemplo, em diagramas de seqüência/colaboração e de estados).

O problema é quando o processo de decisão atinge um nível de complexidade muito grande. Tomemos como exemplo um programa que simula o trabalho de um clínico geral, tentando diagnosticar doenças a partir dos sintomas de um paciente. A princípio, poderíamos fazer o serviço em Java:


if (temperatura >= 37.5 
  && dorRenal
  && exame.possuiBacteriasGramPositivas() ) 
return PIELONEFRITE; 

Mas logo iremos perceber complicações:

  • É preciso suportar um volume muito grande de regras (milhares ou mais).
  • Deve ser possível atualizar as regras de forma simples, e sem a necessidade de parar o sistema.
  • As regras devem poder ser armazenadas em arquivos ou em bancos de dados.
  • A definição das regras deve ser feita por um especialista na área (no caso, um médico). Envolver um programador Java no processo, para cada regra criada ou atualizada, seria muito ineficiente, por isso precisamos de uma “linguagem de regras” relativamente simples.
  • Pode haver ambigüidade. Por exemplo, o mesmo sintoma, como febre alta, pode indicar (ou excluir) centenas de doenças diferentes. Mesmo um conjunto amplo de sintomas pode resultar em diversos diagnósticos possíveis (alguns mais prováveis que outros).
  • Numa implementação simples, em que todas as regras são cruzadas com todos os fatos (no caso, respostas do paciente a perguntas feitas pelo clínico), o desempenho seria muito ruim. Um bom desempenho exige alguma inteligência. Por exemplo executar em primeiro lugar as “melhores” regras: as regras com maior chance de sucesso, as que eliminam mais possibilidades, as menos ambíguas etc.
  • O processo de decisão pode exigir a criação de muitos fatos intermediários; pode também gerar interações complexas e indiretas entre as regras. Por exemplo, se o paciente tem tosse e dor no corpo, deve estar gripado; mas se mais tarde descobrirmos que tem bronquite e foi atropelado, então precisamos, claro, descartar a conclusão sobre a gripe (e quaisquer efeitos colaterais dessa conclusão). Então temos que nos preocupar com as dependências entre regras, e facilitar a programação dessas dependências explicitamente, quando for necessário.

A solução é ter uma linguagem de descrição de regras que responda a esses requisitos, e um ambiente de execução (o engine) que entenda esta linguagem e “execute” as regras cruzando-as com a base de conhecimento.

Neste artigo, vamos adotar como ambiente de execução o JESS 6.1, e como linguagem de regras o CLIPS. Resumidamente, CLIPS é uma linguagem especializada na construção de sistemas baseados em regras, e o JESS é apenas um dos engines de regras implementados 100% em Java, o que torna seu uso muito conveniente para nós. Mesmo sendo um engine dedicado à integração com aplicações Java, o JESS utiliza scripts em CLIPS para definir as regras, de forma que você precisará de alguma familiaridade com a sintaxe dessa linguagem (que iremos explicando ao longo do artigo). Veja mais sobre o engine e a linguagem no quadro “CLIPS, JESS, outros engines e a JSR-94”..

As aplicações que usam engines de regras costumam ser conhecidas como Sistemas Especialistas, pois podem simular o comportamento de um especialista humano. No exemplo, teríamos regras de diagnóstico médico, extraídas do conhecimento de médicos. É claro que máquinas "burras" como os computadores, mesmo alimentadas pelo nosso conhecimento, não possuem inteligência real. Há sistemas especialistas em medicina que superam a maioria dos clínicos humanos em taxa de acertos, assim como há programas de xadrez que vencem grandes mestres internacionais, mas são casos de força bruta vencendo a inteligência.

De qualquer forma, nem todas as aplicações de sistemas de regras precisam ser tão ambiciosas. Aplicações bem convencionais podem usar um engine de regras para resolver alguns problemas específicos, de forma que a maior parte da sua aplicação será escrita numa linguagem como Java. Veja mais no quadro “Aplicando sistemas de regras”.

...

Quer ler esse conteúdo completo? Tenha acesso completo