规则引擎

2009年1月7日

我应该使用规则引擎吗?

规则引擎的全部意义在于提供一种替代性的计算模型。不同于通常的命令式模型,即由按顺序排列的命令、条件语句和循环语句组成的模型,规则引擎基于产生式规则系统。它由一组产生式规则组成,每个规则都有一个条件和一个动作——简单来说,你可以把它想象成一堆 if-then 语句。

微妙之处在于规则可以按任意顺序编写,引擎会使用对它有意义的任何顺序来决定何时评估它们。一个好的理解方式是,系统会遍历所有规则,选择条件为真的规则,然后评估相应的动作。这样做的好处是,许多问题自然地符合这种模型。

  if car.owner.hasCellPhone then premium += 100;
  if car.model.theftRating > 4 then premium += 200;
  if car.owner.livesInDodgyArea && car.model.theftRating > 2 
     then premium += 300;

规则引擎是一种工具,可以更容易地使用这种计算模型进行编程。它可以是一个完整的开发环境,也可以是一个可以与传统平台一起工作的框架。近年来,我看到的大多数工具都是为了适应现有平台而设计的。曾经有人想过使用这样的工具来构建整个系统,但现在人们(明智地)倾向于只在系统的某些部分使用规则引擎。产生式规则计算模型只适用于计算问题的一个子集,因此规则引擎最好嵌入到更大的系统中。

你可以自己构建一个简单的规则引擎。你只需要创建一堆具有条件和动作的对象,将它们存储在一个集合中,然后遍历它们以评估条件并执行动作。但大多数情况下,当人们提到“规则引擎”时,他们指的是专门用来帮助你构建和运行规则引擎的产品。指定规则的技术可以多种多样,从用于描述规则的 Java 对象的 API,到用于表达规则的 DSL,再到允许人们输入规则的 GUI。更高效的执行引擎可以使用专门的算法(例如 Rete 算法)快速评估数百条规则的条件。

规则引擎的一个重要特性是链接——其中一条规则的动作部分会改变系统的状态,从而改变其他规则的条件部分的值。链接听起来很有吸引力,因为它支持更复杂的行为,但很容易导致难以推理和调试。

我遇到过一些使用规则引擎产品的案例,每次似乎都没有取得很好的效果(免责声明:我并不是一个统计上有效的样本)。通常,规则引擎的核心卖点是它允许业务人员自己指定规则,这样他们就可以在不涉及程序员的情况下构建规则。和往常一样,这听起来很有道理,但在实践中很少奏效。

即便如此,业务可读 DSL 仍然有价值,事实上,我确实在这个计算模型中看到了价值。但这里也有风险。最大的风险是,虽然可以很容易地浏览规则列表并看到每条规则都有意义,但规则之间的交互通常会非常复杂——尤其是在链接的情况下。所以我经常听到有人说,建立一个规则系统很容易,但维护起来却很难,因为没有人能理解这种隐式的程序流程。这就是放弃命令式计算模型的弊端。尽管命令式代码有很多缺点,但理解它的工作原理相对容易。使用产生式规则系统,似乎很容易就会出现这样的情况:在一个地方进行简单的更改会导致许多意想不到的后果,而这些后果很少会得到好的结果。

我还没有花足够的时间在这些系统上,无法了解我们应该遵循哪些启发式方法来控制这种隐式行为。

  • 限制规则的数量似乎很重要,事实上,任何规则多到需要复杂算法才能获得良好性能的系统,其规则数量都可能多到无法理解。
  • 要非常小心地使用链接,通常最好组织你的规则以限制甚至消除链接。
  • 与许多地方一样,测试在这里也常常被低估,但隐式行为使得测试变得更加重要——而且需要使用生产数据进行测试。
  • 在构建规则系统时,我会考虑做一些会导致修改规则库时出现早期痛苦的事情。

所有这些都让我觉得,避免使用规则引擎产品有很多理由。产生式规则的基本思想非常简单。为了控制隐式行为,你还需要通过将规则限制在狭窄的上下文中来限制规则的数量。这将支持采用更特定于领域的规则方法,即团队构建一个仅设计用于在该狭窄上下文中工作的有限规则引擎。当然,如果你正在考虑使用规则引擎,我建议你同时使用产品和手工编写的特定于领域的方法进行原型设计,以便你能很好地了解它们的比较情况。

有关构建自己的简单规则引擎的更多信息,包括几个示例,请参阅我的 DSL 书籍中的产生式规则系统章节。