时间:2024-05-04
惠子青,刘晓燕,朱汇龙
(昆明理工大学 信息工程与自动化学院,云南 昆明 650500)
敏捷开发方法是目前非常流行也是非常成功的开发方式[1]。敏捷开发方法要求“在全面的文档中使用文件”.综合的uml图认为代码是次要的,uml的维护成本似乎也说明了代码是次要的。uml图与代码是分开的它只能在文档中体现,当uml图用于增量模型设计时它与源代码之间的协调是非常困难的,增量模型因此也变得非常的复杂,由于uml的类模型很少允许递归,因此几乎没有伸缩性(相反,数据流图允许递归,因为流程就是一个数据流图).另一方面,uml提供了很好的可视化效果,它的好处我们不应该丢弃[6]。一般来说,uml除了成本因素,是非常有利于开发的。
本文描述了一种在代码注释中逐步嵌入图形化UML类模型的严格方法。它是和快速开发的代码一起进行的,最后的结果由简单易懂的类模块组成。该方法从确定我们所需调用的函数开始,每个函数对应一个需求。例如:如果用户需要访问一个购物网站所需的函数可能包括display_product_image()、verify_password()和process_payment(),当一个函数被识别并且驱动非平凡的main(),我们可以称它为-跟设计函数。跟设计函数存在的前提条件是所需要的功能函数存在,而它的后置条件是将跟设计函数需放在开发人员所创建的类中,(后置条件也有可能包括设计约束)。跟设计函数的高阶规范是这篇文章主要讨论的问题。
一般来说,除了成本之外,UML已经被发现对软件开发有利。例如,Vargas发现[3],“在系统层面上,使用类图建模的代码的变化倾向性低于根本没有建模的代码的变化倾向性”
UML在敏捷开发中的应用已经让研究人员关注了一段时间(例如Wei等人[2]),但是还没有出现广泛使用的方法。模型驱动分析指定了用 XML描述 UML模型和元模型的广泛方法,但是这些对于仅仅处理代码的开发者来说并不是立即可见的。Pitkänen和Selonen[10]对增量建模的研究就是一个例子。Marovac在[1]中也将 UML表示为“被标记的句子”而不是数字。Ambler[7]在敏捷建模方面做了非常有用的工作,但是他并没有像在这里展示的那样讨论嵌入UML。他在[8]中指出:“在AM(敏捷建模)的范围内,它(源代码)不会被认为是一个模型,因为我想区分这两个概念。”一些敏捷建模工作的形式(例如Rumpe[5])。一个具体的例子是Karagiannis[4],它描述了敏捷建模方法工程(AMME)。
Weiser显示,开发人员在调试相关的代码片段时使用片段,而这些片段不一定是连续的。在不同的上下文中,本文描述的技术使用切片[11]。
首先,我们现在假设需求是给定的,没有执行已经设计的uml可视化,并且注释源代码是我们唯一了解软件结构的途径。其次,假设代码设计的功能是可用的。在这里我们不假设uml必须以完整的形式显示,如:显示所有类或全部类关系或类的所有方法。
快速开发规范从需求开始当前的输出。在示例中,“用户”可以指定厨房的装修风格,以及收到相应的反馈。例如,这就要求存在一个将橱柜置于某个位置的功能,如place_s_wall cabinet(a_position)。这种功能的识别取决于开发者,在这种情况下,我们将把它们称为“需求”。
根设计功能是执行满足当前所有要求(即通过授权)的功能。所需功能的存在是明确的前置条件。根设计功能依靠这些功能来完成其后置条件,其中包括明确的创建适用于所需功能的类,并将这些类关联起来。这是 UML可视化变得有用的地方。对于快速开发来说,我们所描述的技术就是创建这些UML片段-不需要放置所需的功能。由于这些片段通常只有 1-4个类的组合,所以它们及其相互关系可以作为简单的ASCII数字在评论内提供。 就像每个代码块依赖于前面的代码一样,每个新的 UML片段都应该和已经给出的代码相关联。换句话说,一个连接类可以使模型变得简单。在本质上,根设计函数所要求的功能可以是设计功能递归地重复本文描述的过程。
在本文中,每个函数被分解组织成一个有序的积累的子目标。它们的连接必须满足所有的后置条件。在最简单的情况下,一个子目标可能与后置条件相同。“积累”属性意味着一旦子目标被实现,就通过功能代码的其余部分来维护。这种渐进方式与快速开发是一致的。子目标在代码中用“-”表示。为了方便起见,对于不是必需子目标的逻辑充分性的推理用方括号表示。
开发人员需要确定满足后置条件的子目标,并且选择执行子目标的顺序。另一个是将所需的功能放在合适的位置。
我们选择本文的例子的目标是:它们应该足够小,足以被充分描述和实施,足够复杂以说明技术,并且足够熟悉,以便读者可以将结果与传统起草的UML文档进行比较。因此,我们选择了设计模式的应用程序:抽象工厂和调解器,一般在Gamma等[9]中描述。
例1:厨房的可视化
第一个例子取自[6],使得用户能够以“古董”或“现代”等各种风格可视化给定的厨房布局(在下面的代码中基本上是橱柜布局)。布局由5乘以 5格的橱柜(“f”)和墙柜(“w”),如下例所示:
arrange_kitchen_with(“ANTIQUE”)的执行
产生以下内容:
执行 arrange_kitchen_wit(“MODERN”)
相同的布局产生以下内容:
设计和实施必须很容易适应新的橱柜和新的风格。
(1)选择根设计功能
这个过程是由根设计功能驱动的,执行产生所需的应用程序,并依赖于它在附属(“必需”)功能。根设计函数的如下。
def arrange_kitchen_with(a_style):
前置条件明确地引用了所需的功能——实际绘制或放置各种橱柜的方法风格各异,如下:
Preconditions
1. a_style = 'MODERN' or 'ANTIQUE'
2. place_y_x_cabinet(a_position) are defined,each placing an x cabinet in style y at a_position on the console, where x = floor or wall and y = modern or antique
3. arrange_kitchen() is defined, using place_x_cabinet() with x = floor or wall, to produce a picture of a kitchen on the console.
4. cabinet_arrangement specifies where the flooror wall cabinets should be located on a two-dimensional grid
Postconditions
1.arrange_kitchen()and place_x_cabinet() are allocated
2. place_y_x_cabinet() are allocated for x =floor/wall and y = modern/antique
3. (place_x_cabinet() delegates):
Kitchen.place_x_cabinet() delegates to
place_y_x_cabinet()where y corresponds to a_style
4. A kitchen is displayed on the console as per
cabinet_arrangement and a_style
这些规范依赖于 place_y_x_cabinet()等详细规范的存在。
(2)分解
根设计函数被分解成一系列代码块,每个代码块实现一个子目标(在代码中用“ - ”表示)。 总体来说,子目标声明暗示了整个设计功能的后置条件。当一个子目标标签是有用的,它显示在括号内。
第一个子目标放置函数 arrange_kitchen(),place_floor_cabinet()和 place_wall_cabinet()。 这个子目标的实现由这里描述的UML和Kitchen的代码组成(通常在一个单独的文件中)。
--(Postcondition 1): arrange_kitchen() and
place_x_cabinet()with x = floor or wall are allocated AND
the_kitchen is Kitchen instance with cabinet_arrangement
|_______Kitchen_______|
| arrange_kitchen() |
|place_floor_cabinet()|
| place_wall_cabinet()|
from … import Kitchen
the_kitchen = Kitchen()
the_kitchen.set_arrangement(cabinet_arrangement)
我们接下来找到place_y_x_cabinet(),注意将新的UML片段连接到已经引入的UML。
--(Postcondition 2): place_y_x_cabinet() allocated with x
= floor or wall and y = antique or modern
Kitchen<>---->|_XCabinet_|
_________________________ ^
|____AntiqueXCabinet______|
|____ModernXCabinet______|
|place_antique_x_cabinet()|
|place_modern_x_cabinet()|
现在可以使用place_x_cabinet()函数实施
--(Postcondition 3): place_x_cabinet() delegates
Kitchen<>-x_cabinet---> XCabinet|place()_|
Kitchen.place_x_cabinet() delegates to x_cabinet.place(),
and YXCabinet.place() delegates to place_y_x_cabinet()
where x = floor or wall and y = antique or modern
接下来,开发者可以将Style引入为对象。 方括号表示一个子目标,虽然需要执行,但是在检查子目标的意味着联合后置条件时是不需要的。
--[Style]: the_kitchen.the_style is a Style instance
corresponding to a_style
Kitchen<>-the_style--->|_Style_|
_____________________ ^
|_AntiqueKitchenStyle_||_ModernKitchenStyle_|
’’’
from … import AntiqueKitchenStyle, ModernKitchenStyle
if a_style == "ANTIQUE":
the_style = AntiqueKitchenStyle()
else:
the_style = ModernKitchenStyle()
the_kitchen.set_kitchen_style(the_style)
下一个子目标确保the_kitchen.x_cabinet是设置为适当样式的对象。
--[x_cabinet set]: the_kitchen.x_cabinet is an YXCabinet,
where x/X = wall or floor, and Y corresponds to a_style
Style ---->XCabinet
|get_cabinets()|
YKitchenStyle.get_cabinet() sets the_kitchen.x_cabinet
to a YXCabinet instance
现在的设计足以实现后置条件4,如下所示:
# --(Postcondition 4): A kitchen is displayed
# on the console as per a_style and arrange_kitchen()
the_kitchen.arrange_kitchen()
例2:对接模拟
第二个例子模拟了一艘船的对接,并附有一艘拖船。一个简单的5×5网格就足以证明该方法。我们将指定船舶倾向于1空间的对角线运动,拖船最多可以移动2个空间,但只能垂直和水平移动。例如,下面显示了初始条件,其中d是船的目的地,“0”和s分别表示拖船和船的初始位置。
输出显示如下,每当有重叠时,拖轮位置(数字)占主导地位:
读者将认识到中介设计模式的出现。
(1)选择根设计功能
由于对接是主要的用户故事,因此以下是一个合适的根设计功能:
def dock(a_ship_position, a_tugboat_position,
a_dock_position):
dock()的规范主要涉及所需函数的位置,但是它们也包含一个设计约束(后置条件1)。
Intent: Simulate docking of a ship with tugboat support
Preconditions
1: The parameters are instances of Position
2: a_ship_position is the ship's initial position
3: a_tugboat_position is the tugboat's initial position
4: a_dock_position is the ship's destination
5: move_ship() is defined--the effects of a single move
with specified constraints
6: move_tugboat() is defined–the effects single move with
specified constraints
Postconditions
1. (Independence): The code defining ship and tugboat is
independent of each other
2. move_ship() is allocated
3. move_tugboat() is allocated
4. (Trajectories shown): The ship and tugboat's
trajectories are on the console for the ship traveling in
a minimal path from a_ship_position to a_dock_position
and the tugboat staying as close as possible but out of
the way.
(2)分解
前两个子目标与前两个后置条件相同,并且以避免独立所禁止的相互引用的方式分配所需函数move_ship()和 move_tugboat()。这意味着船舶和拖船之间不应存在随后的依赖关系。
# --(Postconditions 1 & 2): "Independence" observed
# AND move_ship() is allocated
# _________ ___________
# |_Docking_|<>-ship--->|___Ship____|
# |move_ship()|
# --(Postcondition 3): move_tugboat() allocated
# ______________
# Docking<>-tugboat--->|____Tugboat___|
# |move_tugboat()|
下一个子目标履行满足超过相应的后置条件。
# --[Vessel instances have position and heading]
# ________
# |_Vessel_|
# |position|
# |_heading|
# ^
# Ship Tugboat
下一个子目标可以协调。
# --[Each Vessel is aware of its peer]
# ____________________
#
|_VesselCoordination_|<---coordination-<>Vessel
# |_____get_peer()_____|
# ^
# Docking
一旦满足其主要需求,根设计函数通常会通过控制来构建应用程序的设计。
# --(Postcondition 4): Trajectories shown
#
# This is executed by Docking.execute()
from mediator.docking_class import Docking
Docking().execute(a_ship_position,a_tugboat_position,
a_dock_position)
UML的可视化属性可以在敏捷项目中通过建立放置所需函数的设计函数以及将 UML嵌入到有意义的片段中来利用。这不仅仅是一个权宜之计:它减轻了复杂和庞大的UML类模型的不可读性。
这项研究已经预见到了实验和规模的使用,在不断的发展。实际上,大小适中的应用程序需要封装,同样的设计功能驱动的过程应该能够驱动创建包及其使用关系,就像驱动创建类及其关系一样。
不断发展的应用程序开发的主要障碍是可能存在不一致,效率低下以及难以看清整体。本文描述的所需 UML片段允许验证已建立的系统符合嵌入式 UML规范。工具支持是必需的。一个工具将检查一个程序的 UML片段的一致性,并产生一个XML UML描述(以及图)。至少有两种一致性要检查。第一个验证片段集内的自我一致性(例如,使得类A未被显示为从B继承,反之亦然)。第二个检查代码是与 UML片段一致(例如,使得承诺的类A实际上被构造)。
检查一个已建立的系统是否符合其公布的UML的能力并不是特定于本文描述的零碎方法;然而,在持续进化的背景下,开发人员实际上更容易表达“这里是我需要实现当前冲刺的额外的UML”,而不是通过一个大的 UML类模型寻找合适的地方来编辑。
未来的工作将涉及规模项目的设计功能树和碎片化UML的影响。根设计函数假定存在一组函数,其中一些函数本身就是设计函数等等。由此产生的设计函数层次结构可以改善在简介中提到的 UML类模型中递归“伸缩”的缺乏。
[1] N. Marovac. “UML based embedded documentation for semiautomatic software development,” SIGSOFT Softw. Eng.Notes 32.5 (2007), pages 1–3, doi: 10.1145/1290993.1290997.
[2] Q. Wei, G. Danwei, X. Yaohong, F. Jingtao, H. Cheng, and J.Zhengang. “Research on software development process conjunction of scrum and UML modeling,” Proceedings - 2014 4th International Conference on Instrumentation and Measurement, Computer, Communication and Control (IMCCC 2014), 2014, pages 978–982, doi: 10.1109/IMCCC. 2014.206.
[3] R. Vargas, A. Nugroho, M. Chaudron, and J. Visser. “The use of UML class diagrams and its effect on code changeproneness,” Proceedings of the Second Edition of the International Workshop on Experiences and Empirical Studies in Software Modelling (EESSMod '12), Article No. 2, 2012, doi:10.1145/2424563.2424566.
[4] D. Karagiannis. Agile modeling method engineering. 2015,doi: 10.1145/ 2801948.2802040.
[5] B. Rumpe. “Agile Modeling with the UML,” Rissef (2002),pages 297– 309, doi: 10.1007/978-3-540-24626-8_21.
[6] E. Braude. Software design: from programming to architecture.Hoboken, NJ: J. Wiley, 2003, pp133-135.
[7] S. Ambler. Agile Modeling: Effective Practices for eXtreme Programming and the Unified Process. 2002, page 400, doi:10.1017/CBO9780511817533.018.
[8] S. Ambler. Agile/Lean Documentation: Strategies for Agile Software Development. url: http://agilemodeling.com/essays/agileDocumentation.htm# ModelsDocumentsSourceCode.
[9] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design patterns: elements of reusable object-oriented software. Reading,Mass.: Addison-Wesley, 1995.
[10] R. Pitkänen and P. Selonen “A UML Profile for Executable and Incremental Specification-Level Modeling,” «UML»2004 — The Unified Modeling Language. Modeling Languages and Applications Vol 3273, Lecture Notes in Computer Science, pages 158-172, doi 10.1007/978-3-540-30187-5_12.
[11] M. Weiser, "Programmers use slices when debugging,"Communications of the ACM (1982), pages 446-452, doi 10.1145/358557.358577.
我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!