首先来描述一下我们的需求(1):测试的过程被细化为一个一个的STEP,比如测试计划STEP1,测试设计STEP2,测试执行STEP3,测试总结STEP4,是一个顺序的STEP流;测试用例也可以用STEP形式来描述,且STEP可能会更加复杂一些,后一个STEP的输入依赖于前一个STEP,或者后一个STEP是否执行依赖于STEP。前一个是数据流,后一个是条件流。目前,自动化测试中一个测试用例往往用一个测试脚本来实现。现在换一个角度来看,将某个测试用例放大了看,有N多STEP,且一个STEP的操作及其复杂,STEP之间的依赖关系常常改变,那么直接导致的结果是要耗费精力去维护测试脚本。期望能够有一个执行引擎,能依据STEP的之间的依赖关系迭代地执行STEP,并且能够动态地修改这种依赖关系。
从程序语言的角度再来描述一下需求(2):
某个复杂的用例可以这样实现
以下是引用片段: code1 var data if(r1(data)){ step1(data) } if(r2(data)){ step2(data) } … if(rn(data)){ stepn(data) } 这样的程序完成之后n总是有限 code2 var data var r_step_map=[r1:step1,r2:step2…rn:stepn] engine.exe(data,r_step_map){ r_step_map.each{r,step-> if(r(data)){ step(data) } } } |
这里条件和操作都作为数据输入的形式,在增加操作或修改操作顺序或修改操作之间的依赖关系时,不需要修改程序的后半部分,只需要维护r_step_map这个数据即可。
注意,后面这段程序不是某种语言的语法,就目前而言,如果某个语言的语法能像这段程序这样简单就实现这个功能,那么之后的讨论可以不用进行了:)
再往大一点讲,跳出测试,再来描述一下我们的需求(3)。
在软件工程的领域,遇到了如下问题:
(1)程序=算法+数据结构,有些复杂的商业规则很难推导出算法和抽象出数据模型
(2)软件工程要求从需求->设计->编码,然而业务规则常常在需求阶段可能还没有明确,在设计和编码后还在变化,业务规则往往嵌在系统各处代码中
(3)对程序员来说,系统已经维护、更新困难,更不可能让业务人员来管理。
将这个大问题映射到之前的简单问题:
(1)算法简单了看是由条件和操作组成,数据结构就是数据的复杂组织形式;
(2)业务规则嵌套在系统中,也就像第一段程序一样将程序执行的顺序固化下来,尤其对后期变化适应性不好,更别提动态地改变执行顺序
(3)按照code1,程序员与业务员之间是一种紧耦合的工作方式,程序员需要了解所有业务规则,而业务员在规则改变时必须骚扰程序员重写代码;而code2,业务员可以专注维护r_step_map,而开发人员则只需要写好后面的执行引擎,松散耦合,方便维护,提高工作效率
而对于(3)处描述的软件工程领域问题的解决方案之一(没有考证,也许存在其他方案:))是基于规则的专家系统RBES[1]。在参考资料[1]中有比较详尽的描述,总的来说一下其原理。
由组成来看:
Working Memory存储整个过程中用到的数据和产生的数据;
Rule Base存储了规则(包括条件和操作,满足某条件执行某操作即规则)
Inference Engine则是规则执行引擎,通过Pattern Matcher在Agenda维护了待执行的规则列表,Execution Engine执行规则,在Inference Engine中此过程将是一个迭代的过程,而在这个迭代的过程中,可以修改Woking Memory和Rule Base,从而使Agenda可以动态的改变并且使系统拥有自我学习的能力,我想,这就是为什么被称为Inference Engine(推理引擎)。
补充一下,回到简单问题,为了让code2也有推理的能力,稍作修改如下:
以下是引用片段: code3 var data var r_step_map=[r1:step1,r2:step2…rn:stepn] engine.exe(data,r_step_map){ r_step_map.each{r,step-> if(r(data)){ step(data,r_step_map) // 在step里面修改r_step_map } } } |
本文的标题是规则引擎,其实想要描述的是这里的规则执行引擎,而在软件领域规则引擎有其自己的定义:规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据规则做出业务决策。后续文中所说的实现主要关注的是code3中后面engine.exe()部分的实现。
要说实现,必须先提一下规则引擎的困难:
1.人类对事物的推理方法按照思维的进程方向来看主要有演绎、归纳和类比,在规则引擎中所说的forward-chaining和backward-chaining即是演绎法和归纳法。不管哪一种方法,规则引擎的性能决定于推理算法的效率。forward-chaining是从一个初始的事实出发,不断地应用规则(即进行条件判断,执行相关操作)得出结论。backward-chaining则是从假设出发,不断地寻找符合假设的事实。商用规则引擎多半对如何证实一个事实不那么感兴趣,所以现有的商用规则引擎多是forward-chaining的,而有名的RETE算法[3]就是一个高效率的forward-chaining算法。
2.使用规则引擎相比于普通产品,似乎适应性更强,但规则引擎本身对于产品的适应性则依赖于规则引擎是否足够通用。像Java语言系的JSR-94,使得规则引擎的实现标准化,由此产出的规则引擎也更加通用。
3.许多现有的规则引擎,对于规则的描述有自有一套规则语言,虽然也有标准语言RuleML[4],为了规则能够重用,基于此不得不再做一套管理规则语言的转换器,规则语言没有标准化之前,规则引擎很难实现标准化。拿资料[5]中所列的三种规则引擎JRules、JBoss Rules(Drools)和Jess来说一下规则语言,它们或者是采用类XML文本混杂程序语言,但依赖于类Antlr这种解释器;或者使用专用程序语言,学习成本高;而且为了能成为产品,对于规则语言,规则引擎需要提供相应的编辑器和管理工具。就好像在规则引擎中集成了一个IDE。
说明一下,对于困难点1,虽然我们不需要构建规则引擎,但规则引擎之争很关键的一点就是规则引擎的性能。对于困难点2和3,本身对使用者而言是否标准并不重要,但重要的是规则引擎的易用性,如果说使用规则引擎需要耗费的学习成本大大高于不使用规则引擎时对产品维护所需要的成本,那么也没有必要引入规则引擎。
这或许可以说是规则引擎的窘境。但不妨我们回归到规则引擎需求。在技术选型时,很关键要做的一步是清楚明了相关技术的适用范围。
即便在最有利的情况下,在软件系统中编写业务规则也是一项必要的挑战性任务。不明确、不完整、易误解的需求的出现只会使开发工作复杂化,并带来成本高昂的错误。业务规则的可视化和文本描述的结合提供了更准确、更有效地捕捉业务规则的途径,特别对于使用规则驱动引擎的实现来说更是如此。使用本文描述的技术,您就可以使用强大的分析框架更好地武装业务分析人员,便于其沟通复杂的业务处理规则。
基于规则引擎需求,对规则引擎做总结如下:
1.当需要在产品运行过程中不断修改规则,而使得重构产品的成本远远大于引入规则引擎的成本时,则适合引入规则引擎
2.规则引擎的规则编辑和管理面向的用户一般是业务人员,所以规则语言要对业务人员足够有好
3.快速匹配条件从而定位到对应的操作是规则引擎高性能的关键
4.规则引擎已经出现标准化,且有多种解决方案,没有必要重复发明轮子,但使用现有规则引擎实现产品仍然有点门槛
综上,再来看看前文中最初始的需求,我们需要实现的是对于复杂step流式的程序进行step和程序的解耦:
1.需要在运行时不断修改规则,但是引入规则引擎成本过高
2.更改规则的用户是程序员(测试脚本的编写者),不用关心是否要弄一套全新的规则语言,以及这套规则语言的解释器
3.总的来说step执行还是一个顺序流,没有必要维护一个匹配树,即不需要引入RETE算法
4.学习规则引擎标准的成本或许高于给出当前需求下的其他解决方案的实现。
通过前面兜兜转转,这里重新回到对当前需求的技术实现,重复一下需求,要实现如下伪代码的功能:
以下是引用片段: var data var r_step_map=[r1:step1,r2:step2…rn:stepn] engine.exe(data,r_step_map){ r_step_map.each{r,step-> if(r(data)){ step(data,r_step_map) // 在step里面修改r_step_map } } } |
分析一下,r和step都是代码,但在需求中要求作为数据输入,将代码作为数据来看有一种方法叫反射机制。作为java程序员,就以java语言做示例。
对r和step定义统一的接口
以下是引用片段: Interface Rule{ Boolean match(Object data) } Interface Step{ data run(Object data) } |
对应多个实现Rule1,Rule2…Rulen和Step1,Step2…Stepn。将Rule和Step的关系用类名构造成RSMap的键值对。
则执行的代码片段可以写成这样:
以下是引用片段: // data和rsMap已经构造完成 Class r; Class s; Method m; for(String rname:rsMap.keySet()){ r = Class.forName(rname); m = r.getMethod(“match”,Object.class); if((Boolean)m.invoke(r.newInstance(),data)){ s = Class.forName(rsMap.get(rname)); data = s.getMethod(“run”,Object.class).invoke(s.newInstance(),data) } } |
当然实际上data这个数据结构可能复杂的多,而也可以利用Java对象传引用的特点,不需要将每个step的data作为返回值。可以看出已经将固化的代码转化成了类文件+类名和循环执行,可以通过配置rsmap任意增加删减步骤或修改执行步骤的触发条件。
用反射机制,这一系列rules和steps在这个过程执行之前,必须编译成类,并且要放在classpath下,在程序执行的过程中可以通过修改rsMap(暂时这么看,其实java语言在迭代集合时是不能够修改集合的)来增加删减或修改已经存在在类路径下的可用rules和steps。所以还是没有完全满足需求。
本质而言,我们的需求说白了,是要在程序执行时动态地更改在运行的程序的结构,而动态语言[1]就有这样的功能。基于Java的开源动态语言就有许多可选。
BeanShell就是用基于Java反射机制写成的,能够实时执行Java源代码。BeanShell可以作为java程序运行时的Java代码编译器来调用。从原理上来说Beanshell已经能够实现将源代码作为数据输入程序,并执行。
再则不得不提的是Groovy,参考资料[4]中提到的规则引擎的Groovy下的解决方案,本质上是使用了GroovyClassLoader,GroovyClassLoader相当于Groovy下可调用的编译器,这能解决我们的需求,但是使用起来还是有些复杂。而Java有BeanShell,Groovy有GroovyShell,但GroovyShell提供的功能是能够求任意Groovy表达式的值,还没有到达代码级别。Groovy还有GroovyScriptEngine,它能够实现执行任意Groovy脚本,同样可以实现我们的需求。而且编码方面也简单许多,截片段说明如下:
以下是引用片段: def engine = new GroovyScriptEngine(scriptBase) // scriptBase为存放动态Groovy脚本的路径列表 def bind = new Binding() engine.run(scriptName,bind)// scriptName脚本名称 |
由于类型动态engine.run可以返回脚本执行的结果,bind相当于全局数据域,可以存放任意数据,可以将engine甚至bind本身放入bind中,使动态脚本可以使用bind并可以实现递归调用。
以下是引用片段: bind.setVariable “engine”, engine bind.setVariable “bind”, bind |
综上,从小需求出发,寻求解决方案,偏离初衷好几万里,讨论了从现在看来跟初始需求完全没什么关系的规则引擎,再到使用动态语言给出解决方案。所有的过程只是自己学习过程的一个记录。之所以要以规则引擎及其实现作为标题前缀,一来我认为规则引擎的需求简之又简之后,跟我要讨论的需求本质上是一样的,二来,在学习的过程中,在做规则引擎调研的时候发现有许多人和我有一样的需求,但又觉得使用规则引擎或构建规则引擎很复杂,成本上不允许,所以在实现上是否有可替代的方案,总的来说,这也算是不是规则引擎实现方案的方案吧。
百转千回,最后还是要再次惊叹程序语言创造者们的智慧!用语言的特性,让复杂的事情可以简单的解决!
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
内存数据网格提供商一头扎进Java
10年的时间里,应用性能解决方案提供商Alachisoft一直在用NCache(针对N-Tier和网格计算.NET应用的内存计算和数据网格产品)为.NET社区服务。
-
遇到这样一个问题:通过java service wrapper部署应用,wrapper进程占用的内存会一直升高, 直到把内存吃完应用崩溃,但是这个wrapper
遇到这样一个问题:通过java service wrapper部署应用,wrapper进程占用的内存会一直升高 […]
-
Google App Engine for Java 对于目前中国需要学习吗?
-
前无古人后无来者的Java平台
开发人员一直在致力于保持Java的活力,经过20年后,我们感觉从来没有更好的、更令人激动的时刻如同Java社区一样。