2008年11月14日星期五

Rational Edge: 利用 RUP 对遗留系统进行逆向工程

级别: 初级

Philippe Dugerdil, 信息系统系, HEG, University of Applied Sciences

2006 年 11 月 13 日

本文来自于 Rational Edge:许多技术和工具已经被提出来,用于对遗留的信息系统进行逆向工程。但这些技术很少在明确定义过程的环境中被描述过。本文将介绍如何将 IBM Rational 统一过程应用于逆向工程,特别是应用于对系统架构的恢复。

插图 过去的 15 年,软件再造工程已经成为计算机科学中的重要分支学科。较大的企业日益将关键任务自动化,这使得这些业务高度依赖于它们的信息系统。但在很多情况下,这些系统维持了许多年 —— 也就是,这些是对企业很重要的“遗留系统”,但它们经常是很难被理解和维护的。

出于预算的原因,从零开始对这些系统重新开发是糟糕的选择。

另一种选择是再造工程,它可以分解成两个子工程:逆向工程(构建实际代码的表示)以及正向工程(重新构建并/或重新开发代码的一些部分)。 1 特 别地,逆向工程中的一个重要的活动是恢复系统的架构。这是 RUP 强大的地方。在本文中,我将介绍这样一个情况,遗留信息系统的原始开发人员不能提供程序原始结构的信息,并且不能提供可用的文档。我们的过程必须重新构建 共同提供系统的架构视图的统一建模语言(UML)模型。

逆向工程中的一个关键问题是,著名的“概念分配问题”—— 也就是,领域概念到源代码元素的映射。 2 虽然此问题的解决方案依赖于我们所说的“领域概念”,但是我们将展示通过基于 RUP 的规程重新构建软件的 UML 模型能够如何帮助解决该问题。在该过程的最后,我们将能够通过可溯性链接将高层次的业务元素和系统功能链接到代码元素上。

如 同在 RUP 中一样,用例模型的构造对我们的逆向工程过程是重要的。第一,用例模型是用例恢复系统所支持的业务过程模型。第二,分析用例来构建表示软件推测架构的系统 分析模型。第三,用例用作场景的来源,运行以找到业务功能实现中所涉及的软件元素。本文仅仅提供了我们的过程的概述,突出了其主要任务和工作产品。

过程

将 系统逆向工程所需要的大部分任务和工作产品可以在 RUP 中找到。在逆向工程项目中,关键的工作产品是从源代码恢复回来的系统的表示和模型。然而,在某些情况下,任务的相关顺序必须相对于原始的 RUP 过程来修改,因为系统已经开发了。特别的是,随着时间的过去,每个规程的重点的变化将与“零起点”软件开发项目不同。从概念上讲,我们的逆向工程过程通过 以下三个步骤进行:

  1. 估计项目再造工程的范围。
  2. 构建抽象模型。
  3. 恢复软件的架构。

图 1 显示了与这些步骤相关联的任务在传统的二维 RUP 表示中定位于哪里。

图 1:映射到 RUP 规程和阶段上的我们的过程的三个关键步骤

图 1:映射到 RUP 规程和阶段上的我们的过程中的三个关键步骤

在 完整的再造工程的更广环境中,逆向工程项目之后将有一个针对部分或整体重新构建/重新设计/重新开发系统的正向工程项目。这就是要引入新范型的地方,例 如,面向服务的体系结构(SOA)。虽然本文着重于逆向工程部分,但是必须讨论潜在的未来平台或范型,用以评估再造工程项目的可行性。

估计再造工程项目的范围

在当今日益增多的业务灵活性和 IT 成本削减的趋势下,所有再造工程过程都应该试图优化软件资产和 IT 资源。如果再造工程选项与完整重写外包给远程的开发中心的系统竞争,这就尤其真实。

在 此最初步骤中,我们设置再造工程系统的业务范围和所期望的质量属性。通常,这些质量属性是预先设定的,并且重新构造系统以实现它们。然而,如果实际的代码 质量太差,那么可能不值得完全再造工程的工作。管理层可能决定只提取并再造工程某个关键组件。在最坏的情况下,当系统结构如此之坏,以至于没有可以复用的 有用代码,再造工程工作会局限于嵌入于老系统中的知识的提取,以帮助指定新的。此步骤是迭代的:我们对系统的实际结构知道的越多,我们越能够更好地评估系 统重新构造的经济相关性。

如同在任何项目中,首要的任务是为工作产品为前景文档的再造工程系统开发一个表示。特别地,这样的文档 应该阐明项目的基本原理、对再造工程系统的约束条件,以及技术选择。例如,前景文档会将目标架构描述为 SOA。根据项目的大小,前景中的信息可能在将会正确地分析项目的经济相关性的业务用例工作产品中进一步详细描述。这是项目管理规程中的开发业务用例任务 的输出。换句话说,再造工程系统的目标质量属性以及详细的技术约束可能记录在作为需求规程的找到角色和用例任务的输出的补充规范工作产品文档中。由于在逆 向工程项目中,这些规范应用于再造工程的系统中,所以实际的系统必须与这些规范相比较。然而,风险列表将记录没有达到此目标的风险,以及减轻这些风险的方 法。风险列表由项目管理规程的鉴别并估计风险任务生成的(图 2)。

图 2:估计项目的范围

图 2:估计项目的范围

注意到再造工程的系统的目标质量属性的规范不是专门对我们的工作的。例如,它位于 SEI 的 Horseshoe 模型的核心。 3 然而,项目风险的迭代评估似乎没有在少数公开的再造工程过程中得到明确地处理。

构建抽象模型

在 软件架构想法方面的困难是在大多数情况下,在代码层不是明确地表示出来。因此,如果关于系统的信息的唯一来源不在起码的源代码中,那么我们如何能够重新构 建其架构?事实上,软件描述的架构层是设计人员的主要工作,有时候,在技术文档中。当碰到某个未知的遗留系统的源代码时,工程师如何能够知道软件元素的什 么组合将生成某种有意义的架构描述?甚至可能的情况是本来没设计架构!

要例举该悖论,Kazman 和 Carrière 甚至谈到“共享的幻想”来描述遗留系统中软件架构的探索。 4 当然,行业尺寸的软件系统是非常复杂的工件。构建架构模型需要对系统本身的了解,因为其源代码没提供很多帮助。作为对系统了解的辅助,我们可以创建可能在 代码中发现的假设的架构。但是该架构必须足够抽象,以适应我们很可能遇到的大范围的设计。RUP 的系统分析模型可以实现此需求,因为分析类的原型(实体、边界和控制对象)代表我们将在几乎所有信息系统中找到的三个职责。

用户经常对遗留系统有很好的观察。虽然他们可能对程序的设计认识很短浅, 5 但是他们通常很好地意识到软件的业务环境和业务相关性。虽然他们一般不能解释软件的内部工作方式,但是他们通常了解他们使用的程序的目的,以及计算的业务 调整。他们通常知道哪种类型的信息必须输入到系统中,以及什么时候输入。通过从所有涉及的人那里收集系统使用信息,我们可以在业务层重新构造处理序列。总 之,我们可以构建系统打算支持的业务过程的表示,以及推测的领域模型。在 RUP 的术语中,我们重新构建业务用例及其业务分析模型。这是通过 RUP 的业务建模规程的详述业务用例、找到业务工人和实体,及详述业务实体的任务来完成的(参见图 3),但是从带有业务角色和实体的业务过程的实际实现开始的。

图 3:编制所支持的业务过程和领域模型

图 3:编制所支持的业务过程和领域模型

为了帮助找到领域模型,必须分析实际的数据库表并将它们记录在数据模型工作产品中。这是非标准的 RUP 一部分的新的数据库分析任务的结果,但接近于分析和设计规程中数据库设计任务的逆向工程部分(参见图 4)。

图 4:分析实际的数据库架构

图 4:分析实际的数据库架构

与此同时,在执行它们的业务任务时,鉴别出用户执行的系统用例。这令我们通过需求规程的找到角色和用例任务重新构建系统用例模型(图 5)。

图 5:构建用例模型

图 5:构建用例模型

图 6 概括了迄今为止我们重新构建的初始模型。顶端是实际的用户(系统角色),以及我们已经重新构建的用例。这些角色相当于业务分析模型里的业务工人。同时,这 些业务工人执行的任务代表软件支持的业务过程的步骤。在图的底部是重新编制的数据库表格。它们中的一部分相当于业务分析模型的业务实体。

图 6:最初的重构建模型

图 6:最初的重构建模型

在 继续用例的详细内容之前,我们必须根据风险列表和业务用例安排工作。然后,在需求规程的将用例排列优先级的任务中将逆向工程需求(用例)排列优先级,来生 成逆向工程需求属性工作产品,这类似于 RUP 的需求属性存储库(图 7)。如同在传统的 RUP 中一样,在我们评估当前的迭代和所遇到的困难之后更新风险列表(由项目管理规程的评估迭代任务输出的迭代评估工作产品)。

图 7:将用例划分优先级并更新风险列表

图 7:将用例划分优先级并更新风险列表

下一个任务是详述用例(来自于需求规程)来生成所选用例的事件流。然而,由于用例是从用户而不是开发人员恢复回来的,所以它们不太可能完整。但它们仍然代 表系统功能的主要部分。一旦详细描述了某些用例,那么我们就通过 RUP 的分析和设计规程中的标准用例分析任务来构建分析模型(图 8)。

图8:用例细节及分析

图8:用例细节及分析

在 此分析活动中,我们使用 RUP 中编制的映射技术,特别是试探法来从系统用例中找到分析对象。基本上,业务实体成为系统实体,对角色的接口(例如,应用程序的屏幕显示)成为边界对象,并 且协调用例的职责表示为控制对象。图 9 表现出模型元素之间的可溯性链接。该分析模型工作产品代表系统的假设架构。这是我们在这里及时拥有的关于系统架构的最佳猜测。在实际的源代码中,我们可以 期望找到以一种或另一种形式实现边界对象和实体对象的职责的软件元素。然而,控制对象的职责很可能分散在许多软件元素中。总之,此分析模型用于记录我们在 软件中期望找到的内容,只要涉及元素的职责。

图 9

图 9:用例分析模型,及其到其他模型元素的可追溯性链接。这些链接是利用标准的 RUP 试探法构建的。

恢复软件的架构

在 所恢复的遗留系统的抽象结构中(与系统用例相关联的分析模型),我们现在必须找到实现它的信息系统的组件。问题是创建高层的分析模型元素和低层的软件组件 之间的可追溯性链接。我们的方法是“领域驱动”—— 也就是,我们根据所支持的业务任务和功能聚集软件元素。此步骤是一个与传统的 RUP 实践很大不同的步骤,尽管可能,我们将与 RUP 进行一些比较。

此步骤包含以下任务:

  • 分析实现模型
  • 运行用例
  • 分析调用图
  • 将功能映射到实现模型上
  • 验证假设架构
  • 重构高层次架构

分析实现模型

软件架构的重构建的首要任务是编制系统的实现模型。在此阶段,不可能显示模型的元素之间的所有依赖关系。只能表示目录、库、文件、类,和包之间的包含关系。图 10 显示了面向对象的系统的简单结构。在过程程序的情况下,此图将显示文件、库,和目录。

图 10:部分实现模型

图 10:部分实现模型

在标准的 RUP 中,实现模型工作产品是实现规程的实现模型任务结构的输出(图 11)。然而,在我们的过程中,软件架构师回退到代码,以此任务由此得名。该模型将由过程中随后出现的任务补充。

图 11:构造实现模型

图 11:构造实现模型

运行用例

在 此任务中,我们开始鉴别实现用例的代码。首先,我们必须选择业务任务和相关的用例来根据我们设置的优先级列表。由于我们不能用所有可能的输入值运行用例, 所以我们必须只局限于系统用户所建议的典型值。后者在来自于定义执行详情任务的执行用例工作产品中记录。它指定用例的输入参数和执行条件(此工作产品和任 务是类比 RUP 的测试用例和定义测试详情命名的)。然后,我们运行所选择的用例并记录软件的执行轨迹 —— 也就是,执行的功能或方法序列。图 12 表示业务工人及其相应的系统用例。当执行该用例时,记录执行轨迹。这可以通过操作代码、使用调试器,或操作执行环境来完成。 6 这些任务由一个新的角色执行:用例分析员(图 13)。

图 12:用例的执行踪迹被记录了

图 12:用例的执行踪迹被记录了

图 13:执行用例

图 13:执行用例

分析调用图

如 早先提到的,从与用户的交互那里重新获得的用例的事件流不太可能完整。我们当然不应该期望另一种事件流是无移漏的。因此,这样的用例的执行轨迹将不会执行 实际地实现该用例的所有功能。要补充它们,我们必须找到所有由轨迹的功能调用的功能。然后,我们由轨迹的功能开始执行代码的静态分析来构建上下文敏感的调 用图。 7 这样的调用图是配对的(N,R),集合 N 中的节点是功能,R 是“调用”关系,也就是,在功能 f1 和 f2 之间有一条边,如果 f1 的实现包含对 f2 的调用,不论这种调用的条件是什么。要重新获得的功能集合 —— 用例功能的扩展集合 —— 是在运行所有可能的用例场景时可能执行的功能。事实上,功能扩展集合工作产品很可能比严格必需的包含更多功能。在一定程度上,这可以通过关联所有用例的扩展功能集合来分类。

图 14 表示包含了用例的扩展功能集合的调用图。红色的功能是轨迹上的功能,黄色的功能是由前者调用的功能。在下面的文字中,扩展功能集合会由这样一张图表示。该任务由一个新的角色执行,实现分析员(图 15)。

图 14:调用图表示扩展的功能集

图 14:调用图表示扩展的功能集

图 15:分析与所跟踪的方法相关的调用图

图 15:分析与所跟踪的方法相关的调用图

将功能映射到实现模型上

一 旦记录下已知的用例的扩展功能集合,我们就必须将这些功能映射到所恢复的实现模型中。基本上,所有功能都必须是在模型中识别出的某个类或文件中的一部分。 该映射将令我们找到已知业务任务的用例实现中所涉及的实现模型的元素。在图 16 中,所有黄色的类和包都参与了用例 1 的实现,也就是,用户 1 执行的业务任务。

图 16:从可扩展的功能集映射到实现模型中

图 16:从可扩展的功能集映射到实现模型中

在非面向对象的软件中,该图中的类会由文件代替。该任务的输出记录在一个更新的软件架构文档中。此任务由实现分析员执行(图 17)。

图 17:分析从功能到实现模型的映射

图 17:分析从功能到实现模型的映射

验证假设架构

在 过程中的此处,我们知道了用例的实现中所涉及的功能/程序/类/文件/包。现在我们必须使用该知识来验证我们在先前步骤中假设的代码假设架构(分析模 型)。首先,为了任意表格或文件的访问搜索扩展功能集合的源代码。这代表在用例执行过程中实际(或潜在地)被访问的数据结构。其次,一旦找到了这些表格或 文件,包含访问它们的功能的类或文件与分析模型中所期望的相比较。如果必要,必须相应地纠正模型。根据被分析的遗留系统,这些表或文件可能要在程序代码外 面(例如,在 IBM MVS 下运行的 COBOL 程序中,所访问的文件用作业控制语言声明)和/或直接在内部声明。重新获得此信息的技术高度具体到系统,但它通常是可行的,就如每种语言拥有具体到 I/O 的语句用于在代码中搜索。图 18 表示实体对象的验证。一方面,扩展功能集合让我们找到所访问的数据库表格。另一方面,这些 I/O 功能(方法)包含于实现类中。然而后者是与所期望的实体对象对应匹配的。

图 18:验证实体对象

图 18:验证实体对象

接 下来,我们继续边界对象(屏幕显示和接口)。在这种情况下,为所有与屏幕显示相关联的功能搜索扩展功能集合的源代码。然后,将包含类或文件与分析模型中所 期望的内容进行比较,并且,如果必要的话,纠正模型。这些与屏幕显示相关的功能是高度具体到所考虑的程序设计环境和程序设计语言的。通常,它们很好地写在 程序设计手册中,而且它们的识别不应该太难。图 19 表示边界对象的验证。在右边,利用程序设计环境的文档为与屏幕显示相关的功能搜索扩展的功能集合。然后识别出包含的类。这些类与所期望的边界对象(左边) 对应匹配的。

图 19

图 19:验证边界对象

作为第一次猜测,剩余的类(从扩展功能集合到实现模型的映射)是与控制对象相关联的,如图 20 的概要图中显示的。虚线表示从分析模型到实现模型的恢复的可追溯性链接。该任务的输出记录在更新的软件架构文档中。该任务由实现分析员执行(图 21)。

图 20:分析类到实现类的映射

图 20:分析类到实现类的映射

图 21:验证假设架构

图 21:验证假设架构

重构高层次架构

在此活动中,我们必须具体到每个用例的代码分类,并重新构建代码相应的高层架构。首先,我们必须处理类和文件层。然后我们深入到代码中。让我们获得已经逆向工程的用例的集合 UC UC= {UC1...UC2}并让我们定义以下三个功能:

  • classes(UCi):返回与用例 UCi相关的类的集合。这些是包含用例扩展功能集合的类。
  • specific(UCi):返回具体到包含只属于该用例扩展功能集合的功能的用例 UCi的类集合。
  • common({UC1...UCk}):返回对用例 {UC1...UCk} 集合通用的类集合。

然后我们有:

公式 1

用例集合的公共类集合仅仅简单地定义为:

公式 2

这 令我们绘制出用例集合的实现中所涉及的高层元素的图表。图 22 表示对已知用例唯一的元素(与用例颜色一致),及公共元素(绿色)。包含具体到用例的元素的包与用例是一样的颜色。那些包含公共元素的被染成绿色。请注意 这些颜色只与逆向工程的当前状态有关。很可能的是,随着更多用例加入分析中,颜色将会改变。

图 22:分析用例之中具体的和公共的实现类

图 22:分析用例之中具体的和公共的实现类

当 属于目标业务任务的所有用例都得到处理时,我们必须根据它们实现的用例将类(文件)分组,并记录到分析对象的可能映射。一旦对所有用例都完成了,我们就分 析代码的实现模型来检查某些类(文件)的逻辑分组是否已经在代码中存在。例如,这些小组会对应包和/或目录。在可能的分组范型中我们有:

  • 按分析对象类型分组(实例:实现边界的类)
  • 按用例分组(实例:实现已知用例的类分为一组)
  • 按信息来源分组(实例:访问已知数据库的类)
  • 按角色分组(实例:实现了与某个用户角色交互的类)

此外,已知的结构可以同时依据若干标准。由于此步骤,我们可以根据所发现的分 组拟定出代码的高层结构。例如,图 23 表示根据用例的分组,对于 UseCase1 和 UseCase2(Subfunction Use Case 1)的公共子功能用例将已经分析了。然而,该图还显示出一些公共代码可能不能很好地属于子功能用例 (UC-common 1-2) 的实现。

图 23:根据用例将实现类分组

图 23:根据用例将实现类分组

图 24 通过显示与分析对象的一致性将先前的架构表示溶合在一起。该结构可能在代码中找不到并且显示出只用来例举至今为止所恢复的信息。

图 24:根据用例合职责将实现类分组

图 24:根据用例合职责将实现类分组

很重要的是,要注意到即使在程序最初开始时做出某种逻辑分组,也可能出现的情况是,这些分组在系统维护的过程中也不会被考虑。 8 在这种情况下,一些显然的分组可能包含不考虑标准的类。该任务的输出记录在更新的软件架构文档中。此逆向构架任务由软件架构师执行(图 25)。

图 25:重新构建高层架构

图 25:重新构建高层架构

我们的过程总结

表 格 1 概括了我们的过程的主要任务合工作产品。绿色的元素与 RUP 中定义的一样。因为我们的过程的第三个步骤处理实际的代码分析,所以在 RUP 中没有等价的规程。因此,新的数据库分析任务与分析合涉及规程相关。另一方面,由用例的执行重新构造高层架构与实现规程相关。这导致我们在过程中定义两个 新角色:用例分析人员和实现分析人员,以及七个新任务。

表 1:我们的过程的主要任务合工作产品

表 1

本过程中的里程碑

再造工程项目的目标是重新构造遗留软件系统,以便提高一些质量属性。 9 该重构造会暗示技术框架中的变更,例如,到 SOA 框架的移植。不论技术是什么,逆向工程项目应该评估重构造的可行性。事实上,即使逆向工程的目标是对系统重新建模,那么它也必须将某个关键组件的移植定型为新的架构以检查其是可能的。

初 始阶段目的是评估项目的可行性。特别地,项目经理应该检查可以访问并询问足够多的用户,当前的数据库表格合源代码是可访问的,并且了解合适的工具和可用性 (例如,为了记录执行轨迹)。然后,应该将系统的一些关键部分逆向工程,并且验证到新平台的移植路径。最后,必须了解主要的风险。在初始阶段的末尾,可以 做出进行或不进行的决策。

详细描述阶段目的是将系统的关键部分逆向工程,并且重新编制它们的高层架构。特别地,遗留代码的类(文件)的实际分组范型,如果有,应该是知道的。此外,逆向工程环境合工具应该就位。在详细描述阶段的末尾,应该编制出要逆向工程的关键部分的架构。

在构建阶段,将项目范围内的所有组件都逆向工程。然后,在构建阶段的末尾,将系统完整地重新编制。

在转换阶段,将遗留系统的模型合文档传递到将正向工程新系统的团队。

结束语

我 已经介绍了在我们的逆向工程过程中需要的大部分任务和工作产品在 RUP 工具箱中找到或接近它们。值得提到的是在此工作中的用例模型所扮演的重要角色与在任何基于 RUP 的软件开发项目中一样。第一,用例帮助我们重新编制软件所支持的业务过程模型。第二,用例帮助我们构建遗留系统的假设架构。第三,用例是要运行来收集系统 的执行轨迹场景的来源。在某种意义上,用例模型让我们链接了高层业务功能和实现这些功能的低层代码。

将遗留软件系统逆向工程中的关键问题之一是了解代码并构建其架构表示。然而,这两个任务互相依赖,并且问题总是:如何开始?通过 RUP 对 UML 模型重新构造,我已经说明了一个解决方案。

注释

1 参见 IEEE Sorftware 1990 年 1 月的 E.J. Chikofsky 和 J.H. Cross 的“Reverse Engineering and Design Recovery: A Taxonomy”。

2 与 Comm. of the ACM,CACM 37(5),1994 年 5 月的 T.J. Biggerstaff Mitbander 和 D.E. Webster 的“Program Understanding and the Concept Assignment Problem”中描述的一样。

3 参见 J. Bergey 等人的“Options Analysis for Reengineering (OAR): Issues and Conceptual Approach”,Software Engineering Institute, Carnegie Mellon University,Tech。标记 CMU/SEI-99-TN-014,1999 年 9 月。

4 R. Kazman 和 J. Carrière,“Playing Detective: Reconstructing Software Architecture from Available Evidence”,Software Engineering Institute, Carnegie Mellon University,Tech Report CMU/SEI-97-TR-010。1997 年 10 月。

5 F. Abbattista、F. Lanubile,和 G. Visaggio,“Recovering Conceptual Data Models is Human-Intensive,”5th International Conference on Software Engineering and Knowledge Engineering,1993 年。

6 A. Hamou-Lhadj 和 T. Lethbridge,“A Survey of Trace Exploration Tools and Techniques,”Proc. of the 14th Annual IBM Centers for Advanced Studies Conferences (CASCON),IBM Press,多伦多,加拿大,2004 年 10 月。

7 参见 D. Grove 和 C. Chambers,“A Framework for Call Graph Construction Algorithms,”ACM Transactions on Programming Languages and Systems 23(6),2001 年 11 月。

8 P. Tonella 和 A. Potrich,Reverse-Engineering of Object Oriented Code。Springer 2005 年。

9 参见 J. Bergey 等人,前面引用的书。



参考资料



关于作者

author photo

Philippe Dugerdil 是瑞士日内瓦应用科学大学 HEG(Haute école de gestion)的软件工程教授。在这之前,他在软件行业工作了十五年,主要致力于银行业软件部分。他拥有计算机科学的博士学位,以及工商管理硕士学位。 您可以通过 philippe.dugerdil@hesge.ch 联系他。

没有评论: