由于Java语言具有的诸多特性以及Internet和嵌入式系统的普及,对已经投入应用的使用C++语言编写的软件进行Java移植,在软件活动中的比例越来越大以及这一活动过程的特殊性,针对其过程进行的管理控制与开发方法和传统软件活动有所不同,因此讨论了针对移植的策略,并以一个移植项目---LED航显系统移植为例,证明这些策略的有效性?
关键词: 软件工程;移植;Java;软件方法
第1章 绪论
由于Java具有的解释性?可移植性?平台无关性和安全性等特性,使得Java在网络应用?嵌入式系统等对跨平台和安全性有很高要求的领域有了越来越广泛的应用?对于企事业单位来讲,对原有系统进行JAVA化移植,一方面可以避免系统重新开发造成的高成本?长周期?系统融合等不利因素,另一方面可以实现系统的平滑过渡,以较小的成本实现系统的升级?因此,采用软件工程策略来指导整个系统移植的过程是非常有意义的?
第2章 移植方案
移植方案大体可分为3类:功能移植,系统结构移植和实现的完全移植? (1)功能移植:抛弃所有逻辑和语言结构,重新使用目标语言实现源工程的功能?功能移植产生的目标工程的特性独立于源工程,有利于产生高质量的代码和易于维护的工程?但功能移植更像是一个全新的开发过程而不是一个移植过程?它的管理和传统软件过程是类似的,各种生存周期模型和面向对象的软件方法都可以使用? (2)系统结构移植:在类?包级别上保留源工程的大致接口和逻辑结构,对它们的具体实现进行重写?对于一个经历了整个生命周期的源工程,系统逻辑结构是基本满足要求的,针对系统结构的移植能够在保留源工程的基本结构的基础上,使目标工程达到较高的质量?但必须对结构的功能及其之间的联系有深刻的理解,值得注意的是,如果程序员的理解产生了偏差,结果可能是灾难性且难以修复的? (3)实现的完全移植:在保留源工程逻辑结构的基础上尽量保留其实现的语言结构?源工程的代码复用度高,移植工作机械简单?程序员只需要对整体结构作一般了解,其注意力就可以完全集中于自己的工作?开发进度可以精确度量,周期短?效率高?但是,使用实现的完全移植生成的目标工程的质量不会优于源工程?如果缺乏有效的管理,就难以保证工程的质量?基于以上理由,本文主要讨论实现的完全移植策略?没有特殊说明,下文中的移植活动均指实现的完全移植?
第3章 预处理
如果仅仅移植源工程一部分功能,使用代码界定保留需要移植的结构,可以有效地减少需要移植的代码数量?这个过程是简单的,只需针对源工程的所有保留功能进行结构走通并记录调用结构,凡是没有记录下的均可界定在移植范围之外? 定义1:step-代码中去除注释?空行后的有效代码行数,它是系统规模的简单度量? LED航显系统移植项目采用这种界定方式,工程代码量从原来的13362steps,缩减到界定后的11069steps?有效地减少了工作量? 应当注意的是,如果有源工程的开发人员被指派为移植的程序员,这一过程的时间可以大大缩短,甚至略过这一过程?LED航显系统移植项目中,我们使用了1人/月来进行这一阶段得到了足以保证正确移植的有关知识?
第4章 任务分配和管理
定义2:如果某个语言结构A(例如类)的实现依赖于其它语言结构B的实现,则称A在工程中的层次高于B。这种依赖既可能是被继承,方法被调用,也可能作为成员变量或参数的数据耦合等。 在安排工作时,以类作为划分工作的单位,按照层次由高到低的顺序完成,则称之为自顶向下的软件构造方式;反之,则称之为自底向上的软件构造方式。采用自底向上的构造方式只要能保证在某个类代码编写结束之前,它所依赖的类已经完全实现,测试的方式就是简单直观的。所以在LED航显系统移植项目中采用了自底向上的构造方式。 在对移植过程中任务划分之前必须准确度量各语言结构之间的层次关系,在层次关系的基础上进行任务的分配和管理。这一过程往往伴随着对源工程的理解一起进行,并使用层次图的方式进行描述。 定义3:函数C(X)表示语言结构X的层次。 构造层次图的方式有两种: (1)自顶向下的划分 1)如果所有的语言结构均不依赖于某个语言结构A,则记A的层次为0; 2)如果有且仅有i个语言结构X1,X2,X3,…Xi依赖于某语言结构A,则 C(A)=MAX(C(X1),C(X2),C(X3)…C(Xi))+1; (2)自底向上的划分 1)如果某个语言结构A不依赖于任何语言结构,则记A的层次是0; 2)如果某个语言结构依赖于i个语言结构X1,X2,X3,…,Xi 则C(A)=MAX(C(X1),C(X2),C(X3),…,C(Xi))+1; 从形式上来看这两种方式的定义是相似的,但它们的实质是完全不同的。从其层次的标的来看,自顶向下的方式是C(A)<C(B),当且仅当A的层次高于B;自底向上的方法则是完全相反,C(A)>C(B),当且仅当A的层次高于B。构造出的层次图也局部不同。 定义4:一个程序结构的准备度定义为它所依赖程序结构中已经完成的比例。 按照上面的讨论,位于同一层次的类A要先于类B完成,类1、类2、类3、类4的准备度增加了。但是类1、类2、类3位于较高的层次,它们的实现还必须依赖于类4、类6、类7所在层的某个或某些类的实现。完成类A后,直接的效果是类4可以开始进行(是否能够立刻开始依赖于是否还有较低层次的依赖类没有完成)。相比之下类B的完成虽然不能像类A那样对4个类的准备度有所贡献,但直接的效果是类6、类7都可能立即开始进行。对于整个工程来说类B的重要程度应当优于类A。因此,一个程序结构在某层的重要程度首先应当由直接高层的依赖程度决定。 如果采用自顶向下的层次图的生成方式,即标号越小表示层次层越高,一个语言结构的优先函数定义为: F(A)=Σn=1i=1(kci)/N(i)×kn-1 其中:n是语言结构A所在的层次,R(i)是第i层依赖于语言结构A的语言结构个数,N(i)是第i层语言结构的总数。优先函数越大则该语言结构在本层中的重要程度越高,越应当优先完成。而其中的常数K是任意指定的,它反映了相邻层次的依赖之间的重要程度,K越大,较低层次的依赖关系拥有越大的权。合理选择K是必要的,太大的K使得所求得的优先函数值过于集中,而太小的K不利于依赖关系层次的区多类的层次包含的类个数的50%为K。 如采用自底向上的层次构造方法,只是其层次的编号不同,其定义是类似的。 在LED航显系统移植项目中,同时有n个程序员要求任务时,我们根据层次和优先函数挑选出n个任务,根据他们的熟练程度,熟练的程序员被派发较大的任务。基本上没有出现程序员的相互等待。应当指出,无论怎样精巧的安排任务都不能完全杜绝等待的可能。
第5章 代码编写
为了保证接口定义的一致性、代码的可读性以及工程质量,在同一个移植工程中协作的程序员必须遵守相同的移植策略。这些策略应当是一种全局的约定,而且应当以文件的形式固定下来。移植蓝图就是包含了这些策略以及其他相关的内容的一系列文件,它应当包括以下几个部分。 (1)语法结构的移植标准。进行移植时,如何将源语言的结构以目标语言进行重写。以C++到Java的移植为例,这些移植标准有的是易见的,例如相应类型以及流程语句的移植。有些则需要改变原来语言结构,甚至逻辑结构,例如对多继承的移植。有的移植方案是多种的,例如对C++中输入输出参数的移植。移植蓝图中必须指出源工程中涉及到的所有语言结构以及相应的移植方式,对有多种移植方案的应当根据源工程的特点进行选择。 (2)类的管理策略。对源工程的移植的理想情况是类的"一对一"移植,即一个源程序的类对应于一个目标工程中的实现类。但这种情况往往是不可能的,对多重继承以及采用设计包装类的方式解决输出参数的移植都需要新撰写额外的类(将Java中的接口结构也视为一种特殊的类),另外,对于C++中库函数和系统函数的移植可能也需要编写新类加以实现。对于前两种的新增类,往往是从其它类中衍生出来而且数量比较大,在编码开始前就可以预见,可以使用统一的接口和命名规则进行管理。对于第3种情况的新增类,情况就要稍稍复杂一点,不是所有库函数和系统函数的移植都需要新增加类。一种做法是在编码阶段开始时,首先进行的是对工程中涉及到的库函数和系统函数进行移植,然后在此后的移植工作中使用这些结果,是否增加以及如何使用这些新类和今后的工作完全无关;另一种做法是将这一工作推迟到移植使用该库函数或系统函数的代码时,根据上下文由程序员自己决定如何进行移植,是否增加新的类也由程序员自行决定。后一种方式适合规模不大,涉及库函数和系统函数不多的工程,且有明显的缺点:对于同一个函数的移植不同程序员之间可能拥有不同版本,可能造成难以修正的错误,代码的复用度也不高。推荐使用第1种方式。
第6章 代码测试
与传统测试方式不同,在选择测试数据方面,如果源工程中保留了足够的测试数据,这些数据可以直接用于测试。由于移植活动的目标,测试方法可以进行源工程和目标工程的相同执行,观察其动作一致性作为有效的测试手段。测试的重点应当放在源工程的改动之处,必须确保所有的改动没有副作用。 我们采用以类为单位的编码加单元测试的方法,即在每个类的编码完成后,测试人员立刻采用结构走通和基于状态的方法进行单元测试,尽量保证提交的代码,是完整正确的。该测试是尽力而为的,测试人员仅仅保证被测试的代码流程和状态转换方面合乎要求,换言之,有和原来的代码结构相对应的期待动作。所以进一步的测试是必要的。 在对某个类进行编码之前必须从接口调用的角度对需要调用的所有类进行功能和副作用的测试。对于被测试类来说,如果是进行单元测试的同一人员进行测试(即单元测试也是该测试人员进行的)则称为该测试人员的二次测试,若不是进行单元测试的测试人员进行测试,则称为交换测试。无论是二次测试还是交换测试都不是单元测试的简单重复。二次测试或交换测试是一个黑盒测试,测试人员仅仅从即将撰写的类的角度来进行调用测试,不保证没有被调用部分的正确性。对同一个类的每次二次交换测试或二次测试都可能是针对不同侧重点。只要必要,所有被测试类结构在整个二次或交换测试中都会被覆盖到。可以看到,从整个工程的角度来看,一个类如果被调用了n次,它就会被进行n+1次测试,其中1次单元测试,n次二次或者交换测试。 二次测试和交换测试的区别是进行测试的人员和进行单元测试的人员是否相同。根据测试主体的交换性原则,交换测试的效果应当优于二次测试。恰当地安排测试任务,即使不能完全消除二次测试,也能提高交换测试所占的比重。
第7章 总结和展望
如果源工程具有完全不需要了解功能便可机械移植的特性,则完全可以实现机器移植。遗憾的是,C++比较Java来说有更大的灵活性,对于同一结构有更多的实现。完全的自动移植也是不可能的。如何进行机器辅助的半自动化移植,是我们下一步的工作方向。 当前的LED航显系统移植项目仅仅是先移植源工程的一部分功能,形成一个可用版本,然后再进行新功能的添加。后一个过程等同于维护和修改,软件代价无疑是相当大的。如何在进行移植的过程中就一次性进行新功能的增加,也是将来的课题。 |