基于低代码开发范式的Web Service生成方法

2023-01-19 03:54朱绍宏覃章荣
关键词:表达式契约代码

朱绍宏,覃章荣

(广西师范大学 计算机科学与工程学院,广西 桂林 541004)

Web Service[1-4]既是一类基于B/S架构的应用程序,也是为实现团队内部的流程化管理和高效地完成日常决策任务,而专门创建的一套完整的解决方案。由于它需要满足不同部门和人员的业务需求,导致在软件开发过程中,用户需求会频繁地发生变动。传统的软件开发模式缺乏灵活性,不能适应用户需求的快速变化,无法有效地用于Web Service软件的开发。如何高效地开发Web Service程序成为目前业界亟需解决的问题。

在软件开发实践中,常规的解决方案一方面是提供简单易用的软件开发工具,帮助开发者组织资源和减少失误,如Eclipse、IntelliJ IDEA等集成开发环境,经过多个版本更新和技术迭代已经成为开发者的必备工具;另一方面是提供通用代码开发框架,简化软件开发中的模块集成、属性配置等基础工作,如Spring、J2EE框架已经被广泛应用在企业应用的开发过程中。然而,对于Web Service这类应用程序,由于其业务规则复杂多变,开发人员为生成相应软件,仍然需要耗费大量时间和精力编写复杂、臃肿的业务逻辑代码,这为今后程序的部署、维护和升级带来了高成本问题。

为了有效解决上述问题,目前软件开发人员主要使用2类方法以达到提升软件开发效率和目标软件质量的目的。第1类主要是针对于解决软件开发过程中存在大量冗余代码的问题,通过实现代码重用,降低手工编码的工作量,以提升软件开发效率。如Mcmillan等[5]通过从开放源代码库中挖掘特征描述和源代码,实现代码重用,针对软件项目中存在大量通用的功能模块,通过代码重用来有效减少通用模块的编码量,提升软件的开发效率。Perez De Rosso等[6]提出一种用于开发Web应用程序的方法,该方法将独立的、可重用的功能抽象成“概念”,然后由开发人员从“概念”存储库中提取“概念”,通过配置和组合“概念”来构建应用程序。第2类则是为了在需求分析阶段准确地了解用户需求,减少需求变更引发项目返工问题而提出的基于模型的需求验证方法[7-10],通过将用户非形式的需求表述构建成抽象的需求模型,使开发人员能够充分理解用户需求,减少需求错误,保障软件开发过程的顺利开展。在实际应用中,由于对用户的需求进行建模存在较高成本,并且在需求足够清晰之后,模型将会被废弃,这导致建模的投入成本很难取得与之相匹配的回报。

近年来,基于低代码开发范式的方法逐渐受到研究者的青睐。其作为一种可视化软件开发方法,通过提供图形界面的可视化编程,实现将抽象化的模型直接映射为目标软件的实现代码,有效解决建模投入与回报不成正比的问题[11-12]。因此,许多研究人员基于这种方法做了大量研究与探索。李雅雯[13]提出一种将IFML模型转换为HTML和CSS实现代码的低代码方法,可用于解决移动应用开发中难以跨平台的问题。罗异[14]提出一种用于Web程序生成的低代码方法,可用于自动生成Web程序的表现层代码。Viswanathan等[15]提出一种使用活动图和顺序图对工作流程建模,并将模型转换成软件原型的低代码方法,然而生成的原型由面向过程的代码组成,不适用于Web Service程序的开发。Forward[16]等提出一种基于类模型(概念类图)和状态机模型生成应用程序的低代码方法,但是使用该方法生成实际可用的应用程序,仍然需要开发人员手工编写大量代码。Chen等[17]提出一种将自定义XML类型的数据模型转换成应用程序的低代码方法,然而该方法生成的应用程序只能实现简单的增加、删除、修改与查找功能。Kundu等[18]提出一种从UML序列图自动生成一般应用程序代码的方法,首先以序列图来构造一个可视化模型,然后通过设计映射规则将图中的模型元素映射为代码,由于序列图一般不会包含代码层面的信息(如变量的定义、修改、读取、初始化变量以及异常处理等),因此,该方法只能用于生成代码框架。王诗宇等[19]提出一种针对移动用户界面开发的低代码方法,通过定义模型转换的映射规则,实现将MUICM元素转换为Android平台的用户界面代码。Yang等[20-21]为了提升目标软件的质量,针对需求分析阶段容易出现需求错误的问题,提出一种低代码开发方法RM2PT。该方法通过定义用于模型转化的转换规则和转换算法,实现将统一建模语言[22](UML)符号和对象约束语言[23](OCL)表达式构建的需求模型转化成软件原型,能够快速验证用户需求,减少因需求错误造成的不必要损失。然而,由于原型无法实现数据持久化,且不是标准Web Service程序,所以目前还难以进行实际部署与应用。

上述工作为推动软件的高效开发做出了大量贡献,然而,就笔者调查所知,目前还没发现采用低代码方法自动生成标准化Web Service程序的相关文献报道,因此,基于RM2PT的研究工作,本文提出一种直接将需求模型自动转换为Web Service程序的低代码开发方法。该方法建立OCL表达式与数据基本操作的之间的转换规则,结合创建的转换模板和转换算法自动生成数据可持久化的、标准化的Web Service应用程序,从而可有效提高Web Service程序的开发效率与质量。

1 相关知识

1.1 REST

REST[24]是一种互联网架构风格,它使用HTTP协议,并将网络上的所有内容视为资源。同一资源具有多种表现形式,最常见的形式是JSON[25],并且每个资源都有一个唯一的标识,通过这种唯一标识,可以轻松地对资源进行操作。基于REST的体系结构设计,通常使用HTTP的4种方法来表示资源的CRUD(创建、检索、更新和删除)操作:

1) GET:访问资源。

2) POST:创建资源。

3) DELETE:删除资源。

4) PUT:更新资源。

基于REST体系结构的Web Service称为RESTFul Web Service[26],其结构简单,使前端和后端实现解耦,促进了独立开发,也易于维护和测试,在业界得到普遍应用。

1.2 系统操作(system operation)

在绘制系统顺序图(SSD)时,通常会显示出其中的系统事件,即设计系统的事件或I/O消息,而输入的系统事件则表示系统具有用于处理该事件的系统操作[27]。在本文中,业务逻辑的实现代码被封装在系统操作中,对外表现为软件系统在公共接口中提供的操作,用于对输入的系统事件做出处理,并对外输出处理结果。

1.3 需求模型(requirements model)

需求模型是使用RM2PT工具建模之后得到的模型,属于UML的受限子集,包含:

1) 概念类图:类图使用类和对象描述目标系统的结构,而概念类图是通过删除类图中的方法所形成一种变体结构。

2) 操作契约(contract):是使用OCL表达式定义的描述功能需求的一种叙述规范,用来约束UML模型,其包含如下4个部分:

(i) Signature:定义了系统操作名称、输入参数、系统操作的返回类型。

(ii) Definitions:为减少冗余,提取前置条件和后置条件的公共部分作为共享规范,用于变量的复用。

(iii) Precondition:定义了系统执行前应该满足的前置条件。

(iv) Postcondition:指定了系统执行之后应该满足的后置条件。

3) 系统顺序图:呈现了系统事件在一定时间内的发生次序。

4) 用例图:以可视化的方式呈现参与者与目标系统之间的交互行为。

2 Web Service生成方法

关注点分离的原则认为:软件系统必须分解为功能重叠尽可能少的部分[28],即程序不要编写为一个整体,而是将代码分解成多个模块,这些模块是最终确定的系统小块,每个小块都能够完成一个简单的工作。按照此原则,分层体系结构模式成了一种可靠的通用模式,它使Web Service应用具有高内聚,低耦合,易于实现、测试和维护的特点。此外,考虑到当前业界主流的开发架构在企业开发中的受欢迎程度,本文选择Spring Boot软件架构来生成Web Service程序。根据该架构,Web Service程序可分离出如图1所示的系统操作(system operation)、实体仓库(entity repository)、实体类(entity class)以及视图(view)4种核心功能模块。系统操作是应用程序的核心模块,包含业务逻辑的具体实现;实体仓库充当业务层和关系型数据库的交互媒介;实体类是数据交互的载体,用于存储程序内部的信息,应用程序也会将实体类映射为数据库中的数据表;视图用于渲染图形用户界面(GUI)。由于entity类和view的生成过程与文献[21]中的过程相似,因此本文重点是将需求模型转换为实体仓库和系统操作。

图1 Web Service核心功能模块Fig. 1 Main functional modules of Web Service

2.1 总览

低代码(low-code)开发方法使用模型作为业务需求的形式化描述,通过定义的处理逻辑将业务模型转换为可以运行的软件应用程序。因此,本文以Low-Code开发理念为指导,使用RM2PT工具构建的需求模型作为本文方法的转换基础,通过定义用于代码生成的转换规则、转换模板和转换算法,实现直接将需求模型自动转换为Web Service程序。图2显示了本文方法的工作原理,方法包含2种角色——设计者与用户。设计者指代本文方法的提出者,而用户则指代对用户的需求进行建模的需求分析师,其建模得到的需求模型作为本文方法的输入。

图2 自动生成方法的工作原理Fig. 2 Overview of automatic generation approach

在当前的软件开发过程中,编程人员的主要任务是编写目标软件的业务逻辑,业务逻辑中的查找对象、设置引用、创建和删除对象以及更新对象,符合众所周知的CRUD(创建、检索、更新和删除)操作,而OCL具有丰富的描述系统状态的语法机制,可用于描述程序中的业务逻辑状态。因此创建转换规则的目的是建立OCL表达式与数据基本操作之间的映射关系。转换模板用于限定生成代码的格式,使代码符合标准的编码规范,从而提高生成代码的质量。转换模板通过提取分层后的功能模块的代码特征构建而成,因此,图1中的4种功能模块都需要构建转换模板。转换算法是本文用来处理和解析需求模型以及用来生成Web Service程序的一种伪代码描述,后面的章节将详细介绍转换规则、转换模板以及转换算法的实现细节。

2.2 转换规则

图3是应用程序的数据操作示意图,其中,数据访问层和数据库经常受到技术更新换代影响,容易产生较大的变动。而GRASP受保护变量原则认为:预先找出不稳定的变化点,使用统一的接口封装起来,当未来发生变化的时候,可以通过接口扩展新的功能,而不需要修改原有的实现[29]。为了解决这个问题,本文引入了JPA[30]中定义的数据基本操作来降低底层技术更新对程序的影响。此外,目前Web Service程序最常见的操作是从数据库中获取和显示数据[31],而RM2PT中定义的26条转换规则无法生成这些操作。为了生成Web Service程序,需要在这26条转换规则的基础之上,创建生成数据操作的转换规则。因此,本文通过建立OCL表达式与JPA基本操作之间的映射关系,形成了如表1所示的10条新的转换规则。

图3 应用程序数据操作示意图Fig. 3 Illustration of application data operations

表1 转换规则Tab. 1 Transformation rules

(1)save:将执行操作后产生的对象ob保存到ClassName类的对象集合中。

(2)update:在对象属性被赋值之后,需要将对象修改后的属性信息更新到系统中,因此类名为ClassName的对象ob在执行赋值操作之后将会被更新到系统中。

(3)delete:从系统中ClassName类的对象集合中,删除对象实例ob,使系统符合后置条件。

(4)findAll:在系统中查找ClassName类的所有对象。

(5)findById:在ClassName类的所有实例中,通过使用OCL关键词any,以查询条件Id查找对象ob。

(6)findByLike:依据对象的属性值查找系统中具有相似属性值的所有对象。

(7)findByAssociation:从对象ob的关联对象中查找所有符合约束条件o的单个对象ob。

(8)findByAttribute:从ClassName类的所有对象中,通过使用OCL关键词any和约束条件condition(o)来查找单个对象ob。

(9)findByAssociation:从对象ob的所有关联对象中查找符合约束条件o的所有对象实例。

(10)findbyAttribute:通过约束条件o以及OCL关键词select在系统中查询ClassName类的所有对象obs。

在上述转换规则中,基本操作中的检索操作,除了findAll以外,都可以通过任意组合形成更复杂的查询操作。例如,OCL表达式的语义描述为:根据属性name和关联对象teacher查找系统中存在的学生类对象,那么其基本操作可以组合为findByNameAndTeacher,其返回值由OCL关键字决定。通过这种方式,数据查询能够涵盖软件开发中大多数常见的数据访问操作。

2.3 转换模板

Xtend[32]是一种静态类型的编程语言,相比其他JVM语言更加简洁、易读。Xtend不仅提供了一种编写代码生成器的机制,还提供了如类型推断、扩展方法、分派方法和lambda表达式等强大的功能,从而使模型访问和遍历变得非常简单、直观并且易于阅读和维护。这些优点促使本文使用Xtend语言来定义代码的转换模板。本文定义的转换模板包含2种组成成份:第一种是静态成份,是通过提取应用程序的代码特征构建的,此部分可以直接作为代码的组成;第二种是在符号“《 》”中定义的动态成份,它包含模型解析和代码生成的处理逻辑,当这些处理逻辑执行完成之后,其产生的代码将会直接嵌入到模板中,与静态部分组成完整的实现代码。如图4所示,模板A为Web Service类的转换模板(用于封装系统操作),模板B为实体仓库的转换模板,模板C为系统操作的转换模板。

图4 转换模板示例Fig. 4 Transformation templates example

1)在模板A中:@RestController,@RequestMapping这些作为Spring Boot框架的注解用来表示类的属性和作用。在程序部署之后,根据URL地址,服务端首先会定位到实现类,然后再定位到该类中的某一个具体系统操作。因此,在模板A和模板C中定义的@RequestMapping注解将会作为服务的一种资源标识。DaoManage是对象管理器,能够返回实体仓库的引用对象。CurrentUtils是定义的工具类,用来存储和获取临时引用,临时引用是在操作契约中定义的一种特殊对象实例,由多个操作契约所共享,用来减少代码冗余。表达式《name》的输出内容是系统顺序图中的服务名称。《GenerateWSCode()》中的表达式,对应于算法2在生成系统操作的实现代码之后,将代码封装到由模板A所形成的实现类框架的执行过程。

2)模板B使用关键字public,以及《e.name+"Repository"》共同组成一个public接口,“e”为概念类图中的类。此接口会默认继承2个父级接口以实现表1中的(1)~(5)基本操作,而《GenerateRECode()》中的表达式则用于生成(6)~(10)以及它们组合形成的基本操作。

3)在模板C中,“c”为算法2正在处理和解析的操作契约,因此表达式《c.op 》将会动态地解析为当前操作契约的名称。为了生成具有RESTFul风格的Web Service,本文需要为生成的系统操作设置HTTP动词来表示资源的状态扭转。因此,算法2将会根据操作契约的后置条件生成4种HTTP请求方法类型,然后以字符的形式输出,即 《HttpMethod(c)》 将会被解析为“GET”“POST”“PUT”“DELETE”中的一种字符。关键字public、类型String、系统操作的名称《c.op》以及输入变量 《Parameter(c)》 共同组成系统操作的操作签名。表达式 《GenerateSOCode()》 对应于算法2中将操作契约转换为业务代码之后,将代码封装到由模板C所形成的系统操作框架的执行过程。《Return(c)》 中定义的表达式可以被动态地解析为系统操作的返回值。根据MVC原则,视图层不应该包含应用逻辑或业务逻辑,而是应该把请求委派给领域层的领域对象,视图层只需要呈现领域对象返回的处理结果。在程序部署运行之后,视图层以HTTP请求的形式访问领域层,因此,需要考虑领域层到视图层的通讯问题。鉴于JSON作为数据传输的一种理想载体,模板C中定义了用于生成JSON格式字符的处理代码,其中JSONObject是用来创建JSON格式数据的包装类,其引用对象将会包装业务逻辑的处理结果“data”、“msg” 以及“code”状态数据。模板定义了捕获程序异常的try-catch代码,以及将JSON数据转化为字符串并将其作为系统操作返回值的代码。

2.4 转换算法

本文中转换算法主要有3种:第一种为算法1所示的REST生成算法,可以根据系统操作契约的后置条件,输出HTTP的4种请求方法,用于标识系统操作将要响应的HTTP请求类型;第二种为算法2所示的Web Service类的生成算法,将系统操作封装到JAVA类中;第三种为算法3所示的实体仓库类的生成算法。

算法1是REST生成算法,以操作契约作为输入,输出为HTTP请求方法的字符。算法首先创建2个标记Tag和M,然后解析定义在后置条件中的OCL表达式,对其中的OCL表达式执行遍历操作,并判断子表达式的类型。如果子表达式属于删除对象类型的表达式,那么算法将Tag设置为1;如果子表达式属于创建对象类型的表达式,那么算法将Tag设置为2;如果子表达式属于更新对象类型的表达式,那么算法将M设置为1。当遍历完成之后,算法会判断Tag的值,如果值为1则输出Delete字符;如果值为2则输出Post字符;如果值为初始值,算法会判断M的值,如果M的值为1则输出Put字符,否则输出Get字符。按照单一职责原则[33],一个模块应该只有一个引起它发生变化的原因,应该只做一种工作。因此系统操作应该仅包含创建、删除与更新对象3种业务逻辑中的一种功能,但不限于包含一种或多种查找对象的功能。而并不是所有人都会按照此原则编写操作契约,因此设置HTTP方法的优先级是必要的,当前算法处理逻辑的优先级为:Delete > Post > Put > Get。

算法1 REST生成算法。

输入:contract——Contract。

输出:请求方法类型。

1. Begin

2. Tag ← 0

3.M← 0

4. Post ← parse(contract)

5. oclExp ← parse(Post)

6. for sub-exp ∈ oclExp do

7. type ← GetType(sub-exp)

8. if type = Delete type

9. Tag ← 1

10. else if type = Create type

11. Tag ← 2

12. else if type = Update type

13.M← 1

14. end

15. end

16. if Tag = 1

17. Output(“Delete”)

18. else if Tag = 2

19. Output(“Post”)

20. else ifM= 1

21. Output(“Put”)

22. else

23. Output(“Get”)

24. end

25. end

26. end。

算法2为Web Service类的生成算法,算法以系统顺序图、操作契约、Web Service类以及系统操作的转换模板作为输入,输出Web Service类。首先,算法创建一个空集合用于保存操作契约中的OCL表达式(OCL表达式由一行或多行子表达式组成);其次,遍历全部系统顺序图,获取该系统顺序图的系统事件名称op,并根据Web Service类的转换模板生成Web Service类的代码框架;然后,对全部操作契约执行遍历操作,如果当前操作契约的名称与op相同,则根据系统操作的转换模板生成系统操作的代码框架;之后,解析操作契约中的临时对象,如果临时对象不为空,则为其生成引用;接下来,解析操作契约中的definition、前置条件和后置条件部分,如果它们不为空,则会解析其中的OCL表达式并放入到oclSet集合中;接着,遍历oclSet集合中的OCL表达式,将子表达式与转换规则中的表达式做匹配,获取该表达式的类型;此后,根据转换规则将此行表达式转换成为代码。当遍历执行完成,操作契约中的OCL表达式会转换成系统操作的业务逻辑代码,并被算法添加到系统操作的代码框架SO中。接下来,根据操作契约的后置条件生成系统操作的返回值,并添加到SO中。执行完此步骤,当前遍历的操作契约就被成功地转换为系统操作。最后,该系统操作会被封装到Web Service类的代码框架WS中。

算法2 Web Service 类生成算法。

输入:Tws——Web Service类的转换模板;Tso——系统操作的转换模板;CS——全部操作契约;SSDS——全部系统顺序图。

输出:Web Service 类。

1. Begin

2. oclSet ← ∅ ∥创建一个空集合

3. for ssd ∈ SSDS do

4. op ← ssd.op

5. Generate skeleton WS by Tws

6. for contract ∈ CS do

7. if op = contract.op

8. Generate skeleton SO by Tso

9. TP ← parse(contract)

10. if TP<>null

11. Generate reference

12. end

13. Def ← parse(contract)

14. if Def <> null

15. OCLExp ← parse(Def)

16. oclSet.append(OCLExp)

17. end

18. Pre ← parse(contract)

19. if Pre <> null

20. OCLExp ← parse(Pre)

21. oclSet.append(OCLExp)

22. end

23. Post ← parse(contract)

24. if Post <> null

25. OCLExp ← parse(Post)

26. oclSet.append(OCLExp)

27. end

28. for sub-exp ∈ oclSet do

29. type ← Match(sub-exp) ∥匹配转换规则

30. code ← Generate(type , sub-exp) ∥依据转换规则生成代码

31. SO ← code

32. end

33. resultCode ←GetResult (Post)

34. SO ← resultCode

35. WS ← SO ∥将系统操作封装到Web Service类

36. end

37. end

38. end

39. end。

算法3为实体仓库的生成算法,它以概念类图、操作契约以及实体仓库的转换模板作为输入,输出实体仓库的JAVA接口。算法先遍历概念类图中的概念类,每执行一次遍历,都会根据实体仓库的转换模板生成JAVA接口的代码框架(每个概念类都要有一个相对应的实体仓库)。其次,遍历概念类中的关联关系(关联关系包括一对一、一对多、多对一),如果当前概念类属于关联关系中“多”的一方,则根据此关联关系生成findByAssociation基本操作,并将此基本操作的返回值设置为集合类型,否则其返回值会设置为对象类型。然后,遍历全部操作契约的所有OCL子表达式,每次遍历都会设置一个空的集合Set。接下来,分别遍历子表达式中的概念类的属性和关联关系,如果此属性和关联关系属于当前概念类,则将该属性和关联关系存入Set集合。接着,将Set集合中的属性和关联关系组合在一起,并判断子表达式是否包含关键字“any”,如果包含,则生成findByElement这种由多种元素组合而成的基本操作,并将此基本操作的返回值设置为对象类型,否则设置为集合类型。当所有OCL子表达式的遍历执行完成,操作契约中关于此概念类的基本操作就会生成完成。最后,把这些基本操作封装到实体仓库的代码框架ER中。

算法3 实体仓库生成算法

输入:CS——Contracts;ccd——概念类图;T——实体仓库的转换模板。

输出:Entity Repository。

1. Begin

2. for Class ∈ ccd do

3. Generate skeleton ER byT

4. for association ∈ Class do

5. if IsMultipe(association) = true

6. Generate findManyByAssociation code

7. else

8. Generate findOneByAssociation code

9. end

10. end

11. for sub-exp ∈ CS do

12. Set ←∅

13. for attribute ∈ sub-exp do

14. if callClass(attribute) = Class

15. Set ← attribute

16. end

17. end

18. for association∈ sub-exp do

19. if callClass(attribute) = Class

20. Set ← association

21. end

22. end

23. Element ← Connect(Set)

24. if sub-exp include(any)

25. Generate findOneByElement code

26. else

27. Generate findManyByElement code

28. end

29. end

30. ER ← AllCode; ∥将基本操作封装进代码框架

31. end

32. end。

2.5 转换示例

为了对本文方法的实现原理进行说明,引入用户登录案例来介绍需求模型的组成结构和作用,以及如何将模型转换成代码。如图5所示,对该案例建模得到的需求模型包括4种子模型:用例图、概念类图、系统顺序图与操作契约。1)用例图中有参与者User(表示与系统交互的实体)和用例loginService(表示系统需要执行的服务)。2)概念类图使用User类和Role类描述系统结构,图中还展示了Role类与User类的关联关系以及类具有的属性。3)系统顺序图显示了4种系统事件与系统的交互顺序,表示的含义为:系统对象User首先执行register操作,然后选择一种登录方式,打开主界面。4)操作契约封装了系统对 register() 事件进行响应和处理的执行逻辑,它的组成结构有:签名(Signature)、Definitions、前置条件(Precondition)、后置条件(Postcondition)。签名部分包括返回值的类型和3种输入参数:用户名(userName)、密码(pwd)、验证码(code)。Definitions部分定义了2种OCL表达式:根据用户名查询系统中的对象user;获取验证码str。前置条件中定义了系统运行需要满足的3种条件:user必须不存在,str必须存在,code与str必须一致。后置条件中定义了系统运行之后对象的状态变化:首先创建User类的对象u,然后将输入参数赋值给对象u的属性,最后将u保存到系统中。

图5 需求模型示例Fig. 5 Requirements model example

生成代码的示例如图6所示。系统操作中的“POST”请求类型,由REST生成算法,根据操作契约的后置条件转换生成;整个系统操作的方法签名和方法体,由Web Service 类生成算法,根据图5中的操作契约,结合系统操作的转换模板和匹配到的转换规则自动生成;实体仓库由实体仓库生成算法,结合实体仓库的转换模板,根据需求模型中的操作契约和概念类图转换形成;概念类图中的User类可以被转换形成图6中的实体类。

图6 生成代码示例Fig. 6 Generated code example

3 方法的验证

3.1 案例研究

通过互联网检索开源代码库中的源代码,获取到了外卖订单系统(TakeOS)、停车管理系统(Park)、在线教育系统(OE) 3种与日常生活息息相关的Web Service程序,这3个程序的功能需求覆盖面大,可用来验证本文方法建模复杂用例的能力。此外,合作企业提供了已投入使用的机场报修系统(AMS)的源代码,代码中包含了企业内部工作流程的复杂业务逻辑,可用来验证本文方法生成复杂系统操作的能力。本文将在以下部分介绍4个案例需求建模和程序生成的结果。

需求复杂度用于估算面向对象程序的编程成本,可通过统计需求模型中参与者(Actor)、用例(UseCase)、系统操作(SO)、实体类(Entity)以及实体类之间的关联关系(Association)的数量来衡量[21]。因此,本文依据4个案例的源代码反向推导需求模型,然后分别统计案例的需求复杂度,最终得到如表2所示的建模结果: 4个案例总共包括23个参与者,117个用例,194个系统操作,39个实体类,45种类间关联关系。

表2 需求复杂度Tab. 2 Requirements complexity

为了说明本文方法可以用来生成哪种类型的系统操作以及生成过程中的错误处理等问题,以AMS案例的6种功能性需求(如表3所示)为例详细介绍系统操作的分析和处理过程。AMS的故障报修、工单转发、故障受理、完成维修等需求属于业务逻辑中属性赋值、查找对象、设置引用、创建对象、删除对象、更新对象等操作的实现范围,这类需求完全可以使用OCL语句中的系统状态表达式描述,也符合转换规则的转换条件,因此可以为这些需求创建操作契约。对于AMS的附件上传需求,由于其系统操作的实现代码中具有复杂的IO流操作,而这些操作不能使用OCL语句表示,更无法为此需求创建操作契约,因此,本文方法不适用于实现此类需求。保存密码功能需要前端和后端协同实现,但是因为本文方法目前尚不能生成复杂的前端界面交互逻辑,而且创建的操作契约只能生成后端执行代码,无法实现对该功能的完整描述,所以此类需求也不适合使用本文方法实现。按照上述同样的原理或方法,对4个案例的系统操作进行分析,并通过创建操作契约,总结出如表4所示的系统操作生成结果。4个案例总共建模得到194个系统操作,为其中的181个创建了操作契约,占总数的比例约为93.3%,只有约6.7%的系统操作无法实现正确的建模和代码生成。通过对实验结果的分析,需求建模和代码生成的失败情况通常是以下情形:1)对于发送电子邮件、MD5加密、打印文档等功能,如果不调用第三方API,则无法在操作契约中正确指定OCL表达式;2)如保存密码等功能可以通过指定操作契约生成后端代码,但不能为其生成前端交互代码;3)一些系统操作目前只能通过手工编码实现,例如附件上传、发送语音等功能。

表3 AMS案例需求说明Tab. 3 Requirements specification for AMS case

表4 系统操作的生成结果Tab. 4 Generation results of system operations

总之,本文选择的4个案例都具有相对复杂的业务逻辑,可用于验证需求建模以及程序生成的不同方面。而4个案例中93.3%的系统操作可以实现自动生成,证明本文方法自动生成的软件具有很高的实现程度,同时也证明本文提出的10种转换规则结合RM2PT中定义的26种转换规则,适用于生成Web Service程序中的大多数业务逻辑代码。

3.2 功能测试

为了验证生成的系统操作是否正确,需要对自动生成的181个系统操作进行功能性测试。本文选择了如表5所示的实验环境和参数,并使用Junit单元测试框架创建了350个测试用例,这些测试用例能够实现对181个系统操作的单元测试。表6为自动生成系统操作的测试结果,其中GET类型的测试用例有131个,执行成功的数量为127个,成功率为96.95%;POST类型的测试用例83个,测试成功83个,成功率100%;DELETE类型的测试用例为56个,测试成功的数量为40个,成功率为71.43%;PUT类型的测试用例为80个,测试成功80个,成功率为100%。测试用例能够覆盖全部的系统操作,并且能够取得大约94.29%的执行成功率,只有5.71%的测试用例发生了预期之外的错误。

表5 实验环境和参数Tab. 5 Experimental environment and parameter

表6 系统操作的测试结果Tab. 6 Testing result of system operations

测试结果显示,测试失败的用例主要是GET和DELETE类型。通过分析发生的错误信息,发现测试失败的主要原因是:1)当数据表之间存在外键关联时,将无法正常删除该表中的数据;2)如果访问请求中的输入参数与数据表中的类型不匹配,程序将会在运行时发生异常。因此,使用DELETE类型的请求方法会引起第一种错误,可采用软件开发中常用的“逻辑删除”机制应对此问题。应用程序中最常见的数据操作是查询操作,因此使用GET类型的请求方法会有比较大的概率引发第二种错误,可通过修改概念类图中的属性类型解决此问题。

综上所述,测试用例的运行结果显示,大约94.29%的测试用例能够成功执行,这表明从需求模型中自动生成的系统操作具有很高的正确率。此外,因为测试中出现的错误对应于需求模型中的建模缺陷,所以,通过运行测试用例可以快速定位程序运行过程中出现的错误,并根据错误的原因修改需求模型中的建模缺陷,重新生成可靠的应用程序。

3.3 讨论

为了提升Web Service程序的开发效率,本文提出一种低代码开发方法,并取得了较好的实验效果,但方法仍然存在一些局限性,主要包含如下3点:

1)因为本文使用的需求模型属于UML的受限子集,所以适合生成面向对象类型的信息系统。但是本文方法不支持实时系统、嵌入式系统和信息物理融合系统的开发,这类系统需要更复杂的需求模型。

2)本文方法可以提升软件开发效率,但是目前在将用户需求形式化的过程中,采用的是手工编写操作契约方式,可能会产生较高的使用成本。

3)案例研究表明大多数系统操作可以实现自动生成,但是少部分系统操作相对比较复杂,无法满足转换规则的使用条件,需要以手工编码的方式实现。

本文方法目前只针对Web Service程序的开发,但可以通过优化需求模型的组成以使其适用于其他系统的开发。使用操作契约可能产生较高的成本,未来可考虑从自然语言描述的需求规格说明中提取操作契约,以便有效降低操作契约的使用成本。对于存在部分系统操作相对比较复杂的情况,可以通过提供第三方API存储库的方式解决。

4 结语

本文提出一种用于Web Service程序开发的低代码方法,可直接将需求模型自动转换为标准化的Web Service程序。首先,基于RM2PT定义的26条转换规则,通过引入JPA定义的基本操作,构建10条新的转换规则;其次,通过提取Web Service程序的代码特征构建转换模板;最后,建立用于解析和处理需求模型的转换算法。本文通过4个案例评估方法的有效性,实验结果表明,约93.3%的系统操作可以实现自动生成。与传统的软件开发方法相比,本文方法不仅能屏蔽软件实现的技术细节,降低从事软件开发工作所需要的技术门槛,而且还能提高软件的开发效率与质量。

虽然实验结果符合预期,但是本文方法还存在改进的空间,例如编写正确的OCL操作契约会带来一定的使用成本。因此,未来可以从4个方面继续改进生成方法,使其更加完善:

1)从需求模型中自动生成系统操作的测试用例,实现对需求模型的有效验证。

2)为了给用户提供多样化选择,未来将通过优化需求模型的组成使本文方法适用于其他系统的开发。

3)使用自然语言描述用户需求,并实现将自然语言转换成操作契约,让普通用户不需要依赖架构师和开发人员的专业知识就可以生成目标软件,进一步降低技术难度。

4)通过提供第三方API存储库,用户可直接将第三方API集成到操作契约中,以此实现复杂系统操作的自动生成。

猜你喜欢
表达式契约代码
一纸契约保权益
灵活选用二次函数表达式
表达式转换及求值探析
浅析C语言运算符及表达式的教学误区
创世代码
创世代码
创世代码
创世代码
以契约精神完善商业秩序
解放医生与契约精神