在 Rational Software Architect 或 Rational Software Modeler 中使用 JET2 实现模型驱动架构
2010-04-07 00:00:00 来源:WEB开发网引言
让我们来探讨一下,这种改进会带来什么好处吗?
—Beach Boys
如果您就要得到一个项目的初始需求文件,或者能够精确地估计并设计进而开发,会带来什么好处呢?本文告诉您怎样将一个在 IBM®Rational®Software Architect 中创建的统一建模语言(UML),转化成一个动态估计和回馈的引擎。感谢在模型驱动架构(MDA)、设计、静态非描述性图形、模型及图表方面的巨大进步,现在是该执行它们的时候了。Rational Software Architect 和 IBM®Rational®Software Modeler 是 Java Emitter Templates (JET)及 UML 建模的主要推动者。本文试着超越传统的代码生成技术以产生更加有用的系统,例如估计系统。
本文中的 JET 项目描述了以下的任务:
在需求与分析阶段进行估计
在设计阶段将模型当做成熟的形式估计
生成对模型的回馈
模型提供了一个深刻老到的观点,以了解怎样通过注释性的 UML 表达业务问题。一个典型的需求收集包含了以下这些构件:
需求与业务行为通过用例及交流来表达
活动图显示的进程及目标建模
通过结构化的模型表达的信息,例如 Class 和 Component 模型
模型随着时间的增长而不断地发展和成熟。一个从需求开始的典型模型可能还会包括结构化的表达交流的分类模型。一个用例模型就是启动估计过程的完美场。
关于 JET
Eclipse Modeling Framework(EMF),是 Rational Software Architect 的一个完整部分,Rational Software Modeler 包含了生成源代码与文件的强大工具。其中有一款工具就是 JET (Java Emitter Templates)。有了 JET,您就可以使用像 JavaServer™Pages (JSP)这样的语法来书写模板并运行它们。
前提条件
本文面向的读者是那些对 Rational Software Architect 有着详细了解的人群。作者所做的一个假设是读者能够理解 XML、Java™技术,以及 XPATH 脚本语法以及语义。
首个转换:来自用例的项目估计
本文的一部分基于 Capers Jones 在其著作中所描述的发现,这本书就是《估计软件成本:将成本估计带入现实主义》,第二版。您可以查看这本书的第 7 章,“来自敏捷项目及新环境的手动估计方法”。他论述一个用例代表了大约 35 个功能点的平均值,35 只是一个大概值,通常会在 15 及 75 个功能点之间进行变动。1500 个功能点的典型使用(大约 75000 个 Java 声明),大约需要 42 个用例图 。单个用例基本上定义了大约 1785 个 Java 声明的使用模式。
用例估计是 1998-2001 中的基线。大多数的思想领导权来自 1998 年 Schneider 和 Winters 以及 2001 Alistair Cockburn 。本文假设有一个项目团队想要使用用例点来估计,这些用例点书写了目标层次上的用例。因此每一个用例都有一个目标。用户目标层次上用例的目标,与业务价值的一个单元相类似。
用例点是角色总体数量、关系以及用例方法学其他方面的集合体,该用例方法学指示了估计的项目的值,因此组成了 JET2 估计引擎的基础。在决定用例点的规模以后,就可以投入精力与日程安排的规则了。
角色与用例的复杂性
每一个用例都代表 35 个功能点的平均值。图 1 显示了一个在 Rational Software Architect 中创建和概述的简单项目。
图 1. 用例的范例
您可以使用表 1 来决定用例与角色未经调整的权重值。
表 1. 计算未经调整的用例点(角色)
角色类型 | 描述 | 权衡因素 | 角色的数量 | 总体 |
简单的 | 定义良好 API 的外部系统 | 1 | WF * Number | |
平均的 | 使用基于协议界面的外部系统:HTTP 或者 TCP/IP 或者数据库 | 2 | WF * Number | |
复杂的 | 人 | 3 | WF * Number | |
未经调整的角色权衡 | 总体 |
表 2. 计算未经调整的用例点(用例)
用例类型 | 描述 | 权衡因素 | 用例的数量 | 总体 |
简单的 | 1-3 交易 | 5 | WF * Number | |
平均的 | 4-7 交易 | 10 | WF * Number | |
复杂的 | 最少 7 个交易 | 15 | WF * Number | |
未经调整的用例权重 | 总体 |
使用该表来分析一个用例,角色与用例根据主键值来进行定型:简单的,平均的,或者复杂的。
必须创建一个单独的 JET 项目以运行 JET 转换。从 New Project 向导开始:
从主菜单中,选择 File > New > Project。
在 JET Transformations 向导中,选择 JET Transformation Project。
输入一个项目名。例如,使用 Estimation。
点击 Finish。
创建的 JET 含有包含两个文件的一个模板:
main.jet
dump.jet
因为项目将会处理不同类型的估计,创建两个单独的 JET 模板:一个为用例点分析创建,另一个为类点分析创建。
不同类型的角色与用例的统计包含在 Java 脚本集部分中:<% %>
代码会扫描搜索模型中的用例与角色。当代码在模型中找到它们时,它会检查构造型键值,并增加相应的遭遇次数,如代码行 1 所示。
代码 1. 用例点分析
<%--
// Initialize Variables.
--%>
<%
int countUCComplex = 0;
int countUCAverage = 0;
int countUCSimple = 0;
int countActorComplex = 0;
int countActorAverage = 0;
int countActorSimple = 0;
%>
<%--
// Get the name of the model
--%>
<c:setVariable var="model" select="/Model" />
Project Estimates for Model: <c:get select="$model/@name" />
<%--
// iterate through all packages
--%>
<c:iterate select="//Package" var="package" >
<c:if test = "//Package[self::Package]" >
Packages: <c:get select="$package/@name" />
<c:setVariable select="$package" var="source"/>
<%--
// iterate through all use cases in the package
--%>
<c:iterate select="$package/packagedElement[self::UseCase]" var="useCase" >
Use Case: <c:get select = "$useCase/@name" />
<%-- get the stereotypes --%>
<c:iterate select="$useCase/eAnnotations" var="stype" >
<c:iterate select="$stype/details" var="details" >
<c:setVariable select="$details/@key" var="key"/>
Type: <c:get select = "$key" />
<c:choose>
<c:when test = "$key = 'complex'">
<% ++countUCComplex; %>
</c:when>
<c:when test = "$key = 'average'">
<% ++countUCAverage; %>
</c:when>
<c:otherwise>
<% ++countUCSimple; %>
</c:otherwise>
</c:choose>
</c:iterate>
</c:iterate>
</c:iterate>
<%--
// Iterate through all use case actors in the package
--%>
<c:iterate select="$package/packagedElement[self::Actor]" var="actor" >
Actor: <c:get select = "$actor/@name" />
<%-- get the stereotypes --%>
<c:iterate select="$actor/eAnnotations" var="stype" >
<c:iterate select="$stype/details" var="details" >
<c:setVariable select="$details/@key" var="key"/>
Type: <c:get select = "$key" />
<c:choose>
<c:when test = "$key = 'complex'">
<% ++countActorComplex; %>
</c:when>
<c:when test = "$key = 'average'">
<% ++countActorAverage; %>
</c:when>
<c:otherwise>
<% ++countActorSimple; %>
</c:otherwise>
</c:choose>
</c:iterate>
</c:iterate>
</c:iterate>
</c:if>
</c:iterate>
**Totals**
Simple Use Cases: <%= countUCSimple %>, UC Weight: <%= countUCSimple*5 %>
Average Use Cases: <%= countUCAverage %>, UC Weight: <%= countUCAverage*10 %>
Complex Use Cases: <%= countUCComplex %>, UC Weight: <%= countUCComplex*15 %>
Simple Actors: <%= countActorSimple %>, Actor Weight: <%= countActorSimple*1 %>
Average Actors: <%= countActorAverage %>, Actor Weight: <%= countActorAverage*2 %>
Complex Actors: <%= countActorComplex %>, Actor Weight: <%= countActorComplex*3 %>
计算全部未经调整点及个人时间
全部未经调整点是所有个人部分的总结:
未经调整的用例点(UUCP) = 未经调整的角色权重(UAW) + 未经调整的用例权重(UUCW),因此:
UUCP = UAW + UUCW
为了计算个人时间,您必须计算技术方面的复杂性及环境,如代码行 2 所示。
清单 2. 计算未经调整的用例点
<% int totalWeight = countUCSimple*5 +
countUCAverage*10 +
countUCComplex*15 +
countActorSimple*1 +
countActorAverage*2 +
countActorComplex*3;
%>
Unadjusted Use Case Points = <%= totalWeight %>
假设您安装了 Technical Complexity Factor 1.075 版本、Experience Factor 0.545 版本及 Experience / Stability Index 7 版本,那么现在您就可以按照代码行 3 中所示的那样计算个人时间。
清单 3. 未调整的个人时间
<% double personHours = (20 * totalWeight) * 1.075 * 0.545; %>
Unadjusted Person Hours = <%= personHours %>
您可以将 Person Hours 分配给不同的 IBM®Rational Unified Process®(RUP®)阶段,如代码行 4 中的范例所示。
清单 4. 分配给不同的阶段
Project Life Cycle
Inception (5%) = <%= personHours*0.05 %> Hours
Elaboration (20%) = <%= personHours*0.2 %> Hours
Construction (65%) = <%= personHours*0.65 %> Hours
Transition (10%) = <%= personHours*0.1 %> Hours
扩展的转换:类点分析
类点方法提供了系统层次的估计。Class Point 方法的基本想法最初是由 Costagliola、Ferrucci, Tortora 和 G. Vitiello 提出的。提出这种方法的目的是,提供一种方法我们可以用来在开发的阶段不断地精化估计,并利用新的信息直到它们可用为止。
因为功能点需要不断地处理包含用例的规格,所以还可以从中得到一些初步的数据。
用户类的识别与归类
在类点统计的第一步期间,会分析设计规格以识别类并为它们归类。
决定类的复杂性
External Methods 告诉您了类界面的规模。这个规模由本地定义公共方法的数量决定。服务请求提供了不同系统工件联系的评价方法。它同样适用于单个类,并由其他类请求的不同服务的数量决定。
计算总的未调整类点
Class 模型及 Interaction 图回溯至前面描述的用例,如图 2 和图 3 所示。
图 2. Class 模型
图 3. Interaction 图
查看原图(大图)
表 3. 所请求外部方法及服务的复杂性及权衡
权衡 | 复杂性 |
最多 8 个外部方法及单个服务请求 | 简单的 |
最少 8 个的单个服务请求 | 平均的 |
最多 4 个服务请求上最多 4 个的外部方法 | 简单的 |
最多 4个服务请求上 5 个到 8 个的外部方法 | 平均的 |
最多 4 个服务请求上最少 8 个外部 方法 | 高的 |
4 个或者更多服务请求上最少 4 个外部方法 | 平均的 |
所有其他的 | 高的 |
接下来的一步涉及到了从以上描述的各种模型(用例、类模型及显示信息流的交流图)中进行的计算。JET 模板中的代码行 5 负责进行这项工作。它实现了一个 Java 扇区以存储需要的数据。文中还描述了在模板和 Java 代码之间共享数据的一些非常有意思的方法。如代码行 5 所示,代码首先通过了 Class 模型,然后通过了 Interaction 模型,随后又计算外部方法和服务请求 。
清单 5. 类点分析
<%@jet imports="java.util.*" %>
<%
class Additions {
public String className;
public int nem;
public int nsr;
public String lifeLine;
}
java.util.Vector modelClass = new java.util.Vector();
java.util.Vector l = new java.util.Vector();
%>
<c:setVariable select="/Model" var="modelName" />
Model Name: <c:get select="$modelName/@name" />
<c:iterate select="//Package" var="package">
Package Name: <c:get select="$package/@name" />
<%-- Iteration through all classes --%>
<c:iterate select="$package/packagedElement[self::Class]" var="class" >
Class: <c:get select="$class/@name" />
<c:setVariable var="clName" select="$class/@name" />
<% int nems = 0; %>
<c:iterate select="$class/ownedOperation" var="oper" >
Operation: <c:get select="$oper/@name" />
Visibility: <c:get select = "$oper/@visibility" />
<c:if test="$oper/@visibility='public'" >
<% ++nems; %>
</c:if>
</c:iterate>
<c:if test="$class/ownedOperation">
<%
Additions a = new Additions();
a.className = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("clName"));
a.nem = nems;
modelClass.addElement(a);
%>
</c:if>
</c:iterate>
<%-- Iteration through all Interactions --%>
<c:iterate select="$package/packagedElement[self::Collaboration]" var="collab" >
Collaboration: <c:get select="$collab/@name" />
<c:iterate select="$collab/ownedBehavior[self::Interaction]" var="interact" >
Interaction: <c:get select="$interact/@name" />
<c:iterate select="$interact/lifeline" var="lline" >
<c:setVariable var="llName" select="$lline/@name" />
Lifeline: <c:get select="$lline/@name" />
Represents: <c:get select="$lline/represents/@name" />,
<c:get select="$lline/represents/type/@name" />
<c:setVariable var="repClass" select="$lline/represents/type/@name" />
<%
boolean notFound = true;
for (int i = 0; i < modelClass.size(); i++) {
Additions a = (Additions) modelClass.elementAt(i);
String repClassName = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("repClass"));
if (repClassName.equals(a.className)) {
a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("llName"));
modelClass.setElementAt(a, i);
notFound = false;
}
}
if (notFound) {
Additions a = new Additions();
a.className = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("repClass"));
a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("llName"));
modelClass.addElement(a);
}
%>
</c:iterate>
<c:iterate select="$interact/fragment" var="frag" >
Fragment (covered): <c:get select="$frag/covered/@name" />
<c:setVariable var="llname" select="$frag/covered/@name" />
<c:if test="$frag/message">
Message: <c:get select="$frag/message/@name" />
Type: <c:get select="$frag/message/@messageSort" />
<c:if test="$frag/message/@messageSort='synchCall'">
<%
String lifeLine = null;
for (int i = 0; i < modelClass.size(); i++) {
Additions a = (Additions) modelClass.elementAt(i);
lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
(context.getVariable("llname"));
if (lifeLine.equals(a.lifeLine)) {
++a.nsr;
modelClass.setElementAt(a, i);
}
}
%>
</c:if>
</c:if>
<c:if test="$frag/start">
Start: <c:get select="$frag/start/message/@name" />
</c:if>
<c:if test="$frag/finish">
Finish: <c:get select="$frag/finish/message/@name" />
</c:if>
</c:iterate>
<c:iterate select="$interact/message" var="msg" >
Message: <c:get select="$msg/@name" />
Receive Event: <c:get select="$msg/receiveEvent/message/@name" />
Send Event: <c:get select="$msg/sendEvent/message/@name" />
</c:iterate>
</c:iterate>
</c:iterate>
</c:iterate>
Totals:
<%
int countSimple = 0;
int countAverage = 0;
int countComplex = 0;
for (java.util.Enumeration e = modelClass.elements(); e.hasMoreElements();) {
Additions a = (Additions) e.nextElement();
%>
Class: <%= a.className %>, NEMS <%= a.nem %>, Lifeline <%= a.lifeLine %>,
NSR <%= a.nsr %>
<%
if ((a.nem <= 8) && (a.nsr <= 1)) ++countSimple;
else if ((a.nem <= 4) && (a.nsr >= 2) && (a.nsr <= 3)) ++countSimple;
else if ((a.nem <= 4) && (a.nsr > 3)) ++countAverage;
else if ((a.nem >= 5) && (a.nem <= 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage;
else if ((a.nem > 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage;
else ++countComplex;
}
%>
Counts:
Simple: <%= countSimple %>
Average: <%= countAverage %>
Complex: <%= countComplex %>
<%
int tucp = (countSimple * 5) + (countAverage * 8) + (countComplex * 13);
%>
TUCP (Total Unadjusted Class Points) = <%= tucp %>
</c:log>
在您知道全部的未调整点之后,就很容易添加其他的因素以达到未经调整的私人时间,就像您对用例点分析所做的那样。
总结及其他的选项
本文带着您浏览了 UML 模型的一些关键区域,以及怎样获取、转化以及创建添加至智能报告的工件。您可以使用 BIRT (业务智能和报告)来做相似的获取工作。
更多精彩
赞助商链接