试论面向对象软件的维护
吴会松
目录
摘要:
1 问题的提出
2 面向对象的软件易于修改但不易理解
3 面向对象软件的理解、分析
3.1 理解、分析面向对象软件的一般方法
3.2 对(使程序复杂化的)继承机制的分析
3.3 对具有密切关系的类集团的分析
4 面向对象软件的动态联接及多态性
5 几点建议
6 结束语
摘要:
用面向对象的方法开发软件已渐成潮流,人们也普遍认为面向对象软件的维护应不成问题,但事实却非如此。随着面向对象技术的广泛使用,面向对象软件不易维护(原因是维护者不易分析、理解这类软件)的问题已越来越突出。本文在讨论面向对象技术对软件维护影响的基础上,探讨了面向对象软件的维护问题,并提出了解决的对策。
1 问题的提出
所有软件都会有一个代价高昂的维护阶段。很多人的经验表明:少数维护是改正性维护,多数维护属适应性(或改善性)维护。不管一个软件有多么好,用户都会要求有更强的功能、更好的适应性。多年的实践表明:只有不断的维护,软件产品才有生命力。
现在采用面向对象的方法开发软件已渐成潮流,因为面向对象具有一些结构化方法所不具备的优点,对提高软件的开发质量和开发效率极为有益[1]。但面向对象软件也需要代价高昂的维护[2],这种代价往往会超过软件开发时的投入。因为开发时采用的技术不同,软件维护时所面临的问题亦不同,所以我们必须重视面向对象软件的维护问题。鉴于此,本文准备根据面向对象技术的特点,详细探讨面向对象软件所面临的维护问题及我们应该采取的对策。
2 面向对象的软件易于修改但不易理解
同所有软件一样,面向对象软件的维护也需要两个基本条件:①. 待维护的软件可以理解;②..待维护的软件可以修改、在软件维护中,只有先理解程序,然后才能修改程序。
理解程序的关键问题是维护人员能重构"源设计图",并掌握原设计人的设计思想和方法、策略。如果这些思想、方法、策略都集中在一段不大的程序里,那么理解起来还容易一些。但用面向对象技术开发出来的程序往往存在于不连续的程序段中(比如一个方法可能有一段程序,而一个对象中可能有多个各自独立的方法),这时维护者就容易产生错误的理解,并据此作出错误的修改。
面向对象技术的要点,就是把问题抽象成各个对象并封装之[3]。对象内部的消息传递机制、靠消息激活方法的手段,以及对象间的并行、继承、传递、激活等特性,必然会导致软件各部分之间存在着大量的复杂关系。这些面向对象所固有的特色,使程序员们已习惯的阅读、分析、理解程序的方法失去了作用,这也会大大增加理解软件的难度。
而对一个已理解软件的修改却容易得多,因为面向对象软件中的对象能够比较完整地反映客观事物的静态属性和动态行为[4],继承机制又可以有效地使修改量降至最低,再加上对象封装的屏闭作用(对一个对象的修改可以不影响其它对象),所以面向对象的软件易于修改[5]。
综上所述,我们可以得知:如果我们已经理解了一个面向对象的软件,那么这个软件修改起来就很容易(与其它类型的软件相比),但面向对象的软件却不易理解。
3 面向对象软件的理解、分析
3.1 理解、分析面向对象软件的一般方法
理解是维护的基础,而重构"源设计图"则是理解的基础。在重构"源设计图"时,因为对象的多态性、继承机制以及动态联接等特性,给理解原设计增加了困难。为了日后维护的方便,应在开发时就使软件的设计思想易于理解(但由于开发中难以避免的变动,很难保证最终软件的易理解性),软件开发环境也应提供能帮助维护者理解原软件设计思路和策略的工具(眼下这种工具太少)。
按一般习惯理解一个程序段时,应先完成对程序的静态分析,在查阅文档及程序代码的同时,要努力搞清这个程序段的用途及设计思想。维护人员需要查清这个程序段从何处被调用,这段代码所调用的其它过程、调用的前提条件、这些过程的功能及数据流动情况,这样才能真正了解、掌握这段代码。对于面向对象的代码,还必须考虑它的一些特殊点。
为了理解面向对象的软件,应在获取了该软件文档的前提下,分析、研究各对象的组织结构,以及各对象间的相互作用关系,在此基础上遂步识别出程序中的对象、对象间的关系,并掌握各对象中各方法被激活的条件、消息的来源及传递的途径。在现有的技术条件下,有些东西只靠资料往往很难搞清楚,最好找到原开发人员,请他们介绍必须的内容。
3.2 对(使程序复杂化的)继承机制的分析
为了理解程序,就必须搞清楚程序的依赖性(包括其中的调用及数据流的相互关系)。面向对象程序中广泛使用的继承机制为查找、分析程序的依赖性增加了困难。人们在分析一段程序时,必须考虑它是否存在于某些特定的上下文之中?该程序段是否依赖于某个(些)对象类中的方法[6]?让人头痛的是,这个方法有可能是由其子类中的一个成员所操纵的,而操纵该方法的实例变元既可以在类中进行说明,也可以在其子类中的一个成员中进行说明[7]。在图1的示例中,实例变元"职员代码"在"职员"类中说明,可以由其它子类继承。维护小组在分析"技术工"类中的"计算工资"方法时,若遇到"职员代码",就得向上层(可能有多层)类中查找这个变元的说明及注释。
在面向对象软件的开发过程中,随着新对象的不断增加,继承关系的层次也可能会增加,这往往会使继承关系变得更加复杂。例如:维护人员如果需要检查一个消息发送,就需要检查若干层,才能确定该消息的接收者(即某个对象)。这种检查无疑增加了理解程序的难度。
在开发软件时,为避免减弱数据的封装性,同时保证类的功能,程序员们很自然地把对象内的方法做得短小而简单,小方法的数量因此而大大增加。这些大量存在的小方法往往使得系统中的继承关系链变得很长,显然,这也会增加分析的难度。
3.3 对具有密切关系的类集团的分析
面向对象的软件中总有一些关系密切的对象(它们通过密切配合来完成一个特定的任务)[8],笔者把这些对象类称为具有密切关系的类集团(以下简称"密类集团")。维护者要想理解密类集团内每个类中的方法,就必须理解这个密类集团的整个运行机制,这就需要跟踪所有可能的消息传递序列,其工作量相当巨大。因此,密类集团的存在加大了阅读、理解程序的难度。 在目前缺少分析工具的情况下,为了以后的维护,就必须在软件文档中记录好密类集团的活动过程,对各对象中的方法实行跟踪。在测试时,要记录好消息发送的序列、条件、时间、来源及处理结果的去处。这种完整的记录可以减轻以后维护人员分析、理解软件的难度,最终提高软件的可维护性。
4 面向对象软件的动态联接及多态性
动态联接及多态性会给软件维护带来分析上的困难。例如,在图1的示例中,"印工资表"的方法中包含了下面(Visual FoxPro3.0指令)的代码[9]:
this.JSGZ()
这条代码向当前对象发出了"计算工资"的消息。但现在有三个对象都含有名为"计算工资"的方法,对于支持动态联接的语言,显然我们无法根据这条指令就能确定程序会运行这三个方法中的那一个。所有维护者在分析这条指令时都得考虑到这三种可能性。对于我们使用的分析工具来说,也必须对这三种方法进行扫描。
如果只使用静态联接,而且代码的上下文提供了相应的信息,人们可以据此推导出相应的类,但仍有被误导的可能。因为处理一个消息可以用库中的多种方法,一个人又很难记住这些方法对消息的响应,这时出现理解失误的可能性极大。
笔者认为,产生困难的关键是各对象的语义。如果开发者能保证多态一致性,使所有方法对某个消息都产生相同的反映,那么问题就会少一些。但如果方法的名子不一样,维护者仍会产生误解,因为一个方法有少量不同的行为,它们会改变整个代码段的含义。
维护人员可以通过比较"外部依赖图"来发现这种不一致[10]。外部依赖图是一种比较有效的检测工具,当一个方法被启动时,它可以自动地用数据流图的方法来确定可能建立起来的数据项依赖关系。若设计人员能正确地使用多态性,那么具有相同名子的方法就会有相同形式的外部依赖图,这样软件工具就能比较由相同消息启动的方法的外部依赖图,并对不同点作标记,方便维护人员的分析。
对一个软件来说,多态性和动态联接具有不同的两面:优点是使程序具有灵活性[4](这是面向对象设计的目标之一,也是其特有的技术魅力);缺点是给人们理解软件带来了困难,从而增加了维护的难度。为了提高面向对象软件的可维护性,目前还需要有关权威机构建立一个控制、规范动态联接和多态性使用的标准,最大限度地减少这方面的任意性。
5 几点建议
总的来看,面向对象的方法具有很多优点,但在维护上却产生了一些难点,主要是增加了维护者理解、分析软件的难度。 为了减轻面向对象软件维护的难度,综上所述,建议采用以下措施:
a.研制针对面向对象软件特点的维护工具,帮助人们分析、理解软件。
b.软件开发人员在使用面向对象的某些技术(如继承、动态联接等)时要特别小心。因为这些软件机制类似于传统方法中的GOTO语句,虽然有其好处,但使用不当也会带来维护及调试上的困难。
c. 有关权威机构或软件的开发组织应尽快建立一个控制、规范动态联接和多态性使用的标准,最大限度地减少这方面的任意性。在目前缺少权威标准的情况下,开发软件的项目组应先确定自己的标准,把该标准形成文字,作为软件文档的一部分,以减轻后续分析的难度。
d. 开发人员要在文档中作好记录,特别要记录好具有密切关系的类集团的活动及测试过程,使文档能尽量全面地反映软件的情况。
6 结束语
几年前就有学者认为:面向对象的方法必将取代结构化方法。现在看来,"取代"还为时过早,目前见得比较多的还是两者的结合物。因为面向对象的方法还是一种有待完善的技术,目前完全用这种技术开发的软件还不很多,完全面向对象的大型应用软件就更少了,尚未进入全面维护阶段,人们还缺少维护经验。所以我们目前对面向对象软件的维护问题还缺少足够的认识和措施,因此必须抓紧这方面的研究,为未来的大规模维护工作铺平道路。