《软件工程》第9章软件演化

    大型软件系统通常有一个很长的生命周期。企业软件的成本很高,所以一个公司会使用一个软件系统很多年来收回前期对软件产品的投资。因此,那些很多年以前就取得成功的软件和产品,依然会定期发布新版本。

    在系统开发完成后,如果要使其继续有用,对它进行修改是不可避免的。由于业务变更和用户期待的改变,使得对已有系统的新需求浮现出来。由于各种原因,软件的某些部分需要修改,例如,修复运行中发现的错误、适应新的运行平台、提升性能或其他的非功能性特性。所有这些意味着在系统交付后,软件系统总是在不断演化以满足变更的需求。

    企业必须不断改进他们的软件以确保他们能够持续从中获得价值。他们的系统已经成为组织的重要经营资产,必须投资进行系统变更以保持其价值。通常,大多数大型公司在维护系统上的开支要比系统开发上的开支多很多。

    对于一些大规模的企业系统来说,软件演化的代价极其昂贵,因为一个相对独立的系统往往是“由系统构建的(整体)系统”中的一部分。在这种情况下,进行变更需要考虑的不仅仅是系统本身的影响,还有对全局的(整体)系统中其他部分的影响。因此,对某个系统进行变更通常意味着还需要对其他系统也进行相应的修改,以适应环境的变更。

    因此,在确认和分析变更对系统本身产生影响的同时,还需要评估它会对运行环境中的其他系统产生怎样的影响。

    已安装的系统,随着业务和它的环境的改变,其需求也随之改变。因此,系统通常会定期发布新版本以实现改变和更新。因此,软件工程是一个贯穿系统生命周期的,由需求、设计、实现、测试组成的螺旋过程(见下图):开始于系统的第1个发布版本的创建;一旦交付使用,变更提出,则第2个发布版本的开发立刻开始。事实上,甚至于在系统部署之前,演化的需求可能已经变得很明显,以至于在目前的版本发布之前,软件的后继版本就已在开发中了。

    最近10年,软件螺旋式迭代的周期正变得越来越短。在互联 普及之前,软件新版本发布的周期大约是2~3年。而如今面对激烈的竞争和及时的用户反馈,很多软件与Web应用的更新周期甚至可以短至数周。

    这个软件演化模型是针对一个专门组织既负责最初的软件开发又负责以后的软件演化这种情况的。当软件开发完成后,团队需要将工作无缝衔接到软件演化上,并且开发过程中使用的过程和方法会始终贯穿于软件的整个生命周期当中。大部分打包的软件产品是按这种方式开发的。

    对于定制化软件,通常使用另一种不同的方法。一个软件公司为客户开发软件,然后由客户自己的开发团队接管这个系统,即由他们负责软件演化。还有另一种选择是,软件用户和另外一家公司签订一个单独的合同,要求其负责系统的支持和演化。

    在这种情况下,螺旋进程经常是不连续的。需求和设计文档不能从一个公司传递到另一个公司。公司可能通过合并或重组继承来自其他公司的软件,于是发现需求和设计文档都必须进行变更。当从开发到演化的转换不是无缝衔接时,软件移交之后的变更过程被称为“软件维护”。

    另外有一种软件演化生命周期的另一种视图,如下图所示。在这个模型中,他们把演化和维修区别开来。演化阶段涉及软件体系结构和功能性的重大改变;而维修阶段所做的都是一些相对较小但必不可少的修改。

    当软件初步被成功使用时,许多关于需求变化的提议会被采纳和实现。这是演化阶段。但是,随着软件被更改,它的结构也在退化,更改的成本也变得越来越大。这种情况通常发生在使用数年之后,其间也有其他环境的改变,例如硬件或者操作系统。在生命周期的某些阶段软件会到达一个转折点,在此之后软件进行重大的改变以及实现新需求都会变得越来越不划算。这时,软件从演化阶段转为维修阶段。

    在维修阶段,软件仍然是可用的并且一直在使用着,只有一些小的局部的调整需要做。在这个阶段,公司经常会考虑怎样将软件更换掉。在最后阶段,软件仍然在使用,但是只有少量必需的变更会被执行,因而用户需要想办法去绕过发现的某些问题。最终,软件退役并完全退出使用。这通常会包含将旧系统的数据迁移到新的替代系统上,其中会包含一些额外的成本。

§9.1演化过程

    和其他很多软件过程一样,软件变更与软件演化并不存在绝对的标准。软件演化过程在相当程度上依赖于所维护的软件的不同类型和参与开发过程的组织和人。对于有些类型的系统,例如移动应用,演化可能是一种非正式的过程,变更请求大部分来自于系统用户和开发者的交流。而对于其他类型的系统,例如嵌入式关键性系统,这却是一个在每个阶段都产生结构化文档的正式过程。

    在所有的组织中,正式或非正式的系统变更建议都是系统演化的动力。在变更建议中,个人或团队会对现有系统的改进或更新提出自己的建议。这些变更建议可能包括在发布的版本中还没有实现的已有需求、新的需求请求、系统所有者的补丁要求和来自系统开发团队的改进软件的新想法和建议。变更识别的过程和系统演化是循环和持续的,并且贯穿于系统的生命周期(见下图)。

    在接受变更建议之前,需要对软件进行分析,以确定哪些构件需要更改。该分析允许评估变更的成本和影响。这是变更管理的一般过程的一部分,它还应确保每个系统版本中都包含正确版本的构件。

    下图展示了演化过程的概况。演化过程包括变更分析的基础活动、版本规划、系统实现和对客户发布。通过评估这些变更的花费与影响,来发现系统在多大程度上受到影响以及实现变更可能花费多少。

    如果所提议的变更被接受,则会规划一个新的系统发布版本。在发布规划期间,考虑所有提出的变更建议(故障修复、适应和新功能),然后决定在系统的下一个版本中实现哪些变更。接下来实现并确认变更,并发布一个新的系统版本。这之后,该过程会进入下一个迭代,考虑针对下一个发布所提出的一组新的变更。

    在有些情况下开发和演化被集成到了一起,此时变更实现只是开发过程的一个迭代。针对系统的各种修改进行设计、实现和测试。初始开发和演化之间唯一的区别是,交付后的客户反馈在规划一个应用的新发布版本时必须进行考虑。

    而当开发和变更过程是由不同的团队各自负责时,最重要的不同点在于,负责变更工作的团队首先要理解源代码的实现方式。在这个阶段,要了解程序是怎样构造和实现它的功能的。在实现一个变更时,我们要利用以上的理解来确保实现的变更不会对现有系统产生不利影响。

    如果需求规格说明和设计文档是存在的,那么在实现变更后,应该对其进行修改以反映系统的变化情况(见下图)。新的软件需求也应该被记录下来,用于分析和确认。如果设计文档中还包含UML模型,那么也应该对其进行相应的修改。这些工作或许应该在需求变更的分析阶段进行,这样就能有效地评估变更的花费和影响。

    变更请求有时关系到需要立刻解决的一些系统问题。以下这些原因可能会导致出现紧急的变更。

    1.一个严重的系统缺陷被发现,必须修复该缺陷以使常规的系统运行可以继续或者解决一个严重的信息安全漏洞。

    2.如果系统操作环境的变更中有不期望的情况发生,那么就会破坏正常的操作。

    3.如果系统上运行的业务有未预料到的改变发生,例如,有新的竞争对手出现或者有新的法律生效并影响到了系统。

    在这些情况下,就需要做出快速的变更,也意味着不能遵循正常的变更分析过程——不是按正常的过程去修改需求和设计,而是要对程序进行紧急的修补来解决突发问题(见下图)。这样做的危险是使需求、软件设计和代码逐渐变得不一致。尽管你打算记录需求和设计方面的变更,却可能又有一个急需的修补在等你完成。它的优先权高于文档记录。最后,最初的变更被遗忘了,系统文档与代码再也不能一致了。这其中存在的一个问题就是,维护大量的文档与敏捷开发中尽可能减少文档数量的基本目标相冲突。

    紧急系统修补通常需要尽可能快地完成。应当选择一种快速的可行方案,而不是选择一个能保证系统结构最好的方案。这就加速了软件的老化过程,并使未来的变更计划更困难,维护费用上升。在理想的情况下,在紧急修补完成后,最好应该再对代码进行重构,以提高代码质量和避免软件老化。当然,修补代码可能再次得到利用。如果我们有更多的分析时间的话,就有可能找到此问题的另一个更好的解决方案。

    敏捷方法和过程也许会在程序演化和程序开发中用到。实际上,因为这些方法是基于增量开发的,敏捷开发向交付后演化的转变应当是无缝衔接的。然而,当一个开发团队向另外一个负责演化的团队交接时,有以下两种问题可能出现:

    1.开发团队运用了敏捷方法,但是演化团队却不熟悉敏捷方法而选择了一个计划驱动的方法。演化团队可能期望详细的文档来支持演化工作的进行,而这恰恰是敏捷方法所不能提供的。可能没有关于系统的一个明确的描述以供变更时使用。

    诸如测试驱动开发和自动回归测试这样的敏捷方法可以在系统变更时使用。很多系统变更会以用户故事的方式被提出,这些用户的参与可以为确定系统变更的优先级顺序提供极大的帮助。Scrum方法中关注待处理工作的特性也能帮助确定最重要的系统变更。总之,软件演化过程仍然需要一些敏捷开发方法。

    然而,当用于程序维护和演化时,在开发中所使用的的敏捷方法必须进行一些修改。在实践中让用户参与开发团队是不太可能的,因为变更提议来自范围很广的利益相关者。短开发周期可能不得不被打断来处理紧急修复,发布之间的差距可能不得不拉大以避免干扰运行过程。

§9.2遗留系统

    大型企业一般在20世纪60年代开始将他们的运营计算机化,因此在过去的大约50年中,越来越多的软件系统被引入到这些企业中。这些系统中许多都已经随着业务的变更和演化被替换掉了(有时候会多次替换)。然而,很多旧系统仍然还在使用并且在业务的运行在扮演着重要的角色。这些旧软件系统有时候被称为遗留系统。

    遗留系统是比较老的系统,它们依赖于一些在新系统开发中不再使用的语言和技术。典型情况下,它们已经被维护了很长一段时间,它们的结构可能已经由于所做的修改而发生了退化。遗留软件可能依赖于更老的硬件,例如主机计算机,而且可能与遗留过程和规程相关联。这些软件可能无法通过变更来适应更加有效的业务过程,因为遗留软件无法被修改来支持新的过程。

    遗留系统不仅仅是软件系统,还是更广阔的 会技术系统,其中包括硬件、软件、各种库,以及其他支持性的软件和业务过程。下图描述了一个遗留系统的逻辑成分以及它们的关系。

    1.系统硬件。遗留系统当初开发的时候可能是针对一些老的硬件编写的,这些硬件现在已经不再可用、维护很昂贵,并且可能与当前的组织信息技术采购政策不相符。

    2.支持软件。遗留系统可能依赖于一系列支持软件,从操作系统以及硬件厂商提供的一些工具到用于系统开发的编译器。这些软件也可能过时了,它们原来的提供者可能不再提供支持了。

    3.应用软件。提供业务服务的应用系统通常由一些应用程序组成,这些应用程序是在不同的时间开发的。其中一些程序将会同时成为其他应用软件系统的一部分。

    4.应用数据。这些数据是应用系统处理的。许多遗留系统都在漫长的系统生命周期中积累了大量的数据。这些数据可能会不一致,可能会在多个文件中重复出现,还可能分散在一些不同的数据库中。

    5.业务过程。这些过程在业务中使用以实现一些业务目标。业务过程可能会围绕一个遗留系统设计并且受该系统提供的功能的制约。

    6.业务政策和规则。这些政策和规则包括关于业务应当如何开展的定义以及对于业务的约束。遗留应用系统的使用可能会蕴含在这些政策和规则中。

    另一种看待这些遗留系统组成成分的方式是分一系列的层,如下图所示。

    每一层都依赖于紧挨着的下一层并且与该层有接口。如果能够保持接口不变,那么应该可以在一层之内进行修改而不会影响相邻的层。然而,在实践中这种封装过于简单化,对于系统中某一层的修改可能会要求对所改变的那一层的上一层和下一层都进行修改。其中的原因包括以下3个方面:

    1.修改系统中的某一层可能会引入新的工具,系统中更高的层可能会进行修改以利用这些新的工具。

    2.修改软件可能会使系统变慢,从而需要新的硬件来改进系统性能。来自新硬件的性能提升接下来又意味着原来不现实的对软件进一步的修改成为可能。

    3.维护硬件接口经常是不可能的,特别是当新的硬件引入后,这在嵌入式系统中尤其棘手,在这类系统中软件和硬件之间存在着紧密的耦合。为了有效利用新的硬件,应用软件需要进行大幅度的修改。

    一些遗留系统使用的是已经不再广泛应用的古老编程语言开发的,随着时间的推移,很多原来的系统开发与维护者已经退休,而更年轻的软件工程师对使用现代的编程语言更感兴趣,这就导致很难找到拥有编写这种古董编程语言技能的程序员。技能的缺乏仅仅是维护业务遗留系统的众多问题中的一个。其他问题还有:信息安全漏洞,因为这些系统是在互联 广泛使用之前开发的;与用现代变程序员编写的系统进行接口交互时所面临的问题。最初的软件工具商可能已经退出相关业务,或者不再维护当初用于开发系统的支持工具。系统硬件可能会过时并且越来越贵。

    而企业使用更加现代化的同类系统替换这些遗留系统有太贵并且风险太大的弊端。如果一个遗留系统还能够有效工作,那么更换系统的成本可能会超过来自新系统支持成本减低所节省的费用。将遗留系统废弃掉并用更加现代的软件来替换它们,很可能会使事情走上歧途,或者出现新系统无法满足业务需要的情况。管理层试图将这种风险最小化,因此不想面对新软件系统所带来的不确定性。

    用新系统替换遗留系统昂贵并且风险很高的原因主要有:

    1.遗留系统很少有一个完备的规格说明。最初的规格说明可能已经遗失。如果存在一个规格说明,那么也很有可能并没有及时更新以反映在该系统上实施的所有修改。因此,没有什么直观的办法可以让我们去刻画一个与正在使用的系统功能上等价的新系统。

    2.业务过程以及遗留系统的运行方式经常不可避免地交织在一起。这些过程很可能已经演化得可以利用软件的服务,同时对软件的缺点进行了变通。如果系统被替换掉,那么这些过程也必须变化,这其中包含不可预测的成本以及后果。

    3.重要的业务规则可能会蕴含在软件中,并且可能并没有进行专门的文档描述。一条业务规则是一个适用于一些业务功能的约束,违反约束可能会对业务造成无法预计的后果。

    4.新的软件开发从内在来讲也是充满风险的,因此新系统中可能会存在无法预见的问题。新系统可能会无法按时以及按照预计的价格交付。

    继续保持使用遗留系统可以避免系统替换的风险,但是随着系统逐渐老化,在已有的构件中进行修改可能会变得越来越昂贵。对使用多年的遗留软件系统的修改尤其困难,这主要是由于以下这些原因:

    1.程序风格和使用习惯不一致,因为系统的修改是由不同的人负责的,这加剧了理解系统代码的困难。

    2.系统的一部分或全部可能是用过时的编程语言实现的。找到懂这些语言的人可能会很难。因此,可能需要花费昂贵的代价寻找系统维护外包。

    3.系统文档化经常不充分而且过时。有时候,系统唯一可用的文档就是系统源代码。

    4.多年的维护通常会使系统结构退化,使理解系统越来越难。新的程序可能是通过临时的方式添加进去并与其他部分交互的。

    5.系统可能针对空间利用或执行速度进行了优化,从而使系统在更老且更慢的硬件上能够有效运行。这通常都会使用特定的机器和语言进行优化,这些优化通常都会导致软件难以理解。这对于学习现代软件工程技术且不懂在程序中使用的那些编程技巧的程序员而言是个问题。

    6.系统所处理的数据可能会保存在多个结构不匹配的文件里。其中可能存在数据重复,数据本身可能会过时、不准确、不完整。可能会使用多个来自不同供应商的数据库。

    到了某一阶段,管理和维护遗留系统的成本会变得如此之高,以至于不得不将现有系统替换为一个新系统。

    9.2.1遗留系统管理

    对于使用现代软件工程过程(如敏捷开发和软件产品线)来开发的新软件系统而言,规划如何将系统开发和演化集成到在一起是可能的。越来越多的公司开始认识到系统开发过程是一个全生命周期的过程,人为地将软件开发和软件维护分开并不好。但是,仍然存在许多遗留系统,它们是十分重要的业务系统,我们必须扩展和调整它们以适应变化。

    大多数组织拥有很多遗留系统,对这些遗留系统的维护和更新的资金又非常有限,这时候就需要对投资做精心的安排,以期获得最佳的回 。这就要求组织先对遗留系统进行现实的评估,然后做出适当的决策,有以下4种基本选择:

    1.彻底废弃这个系统。当系统不能对业务过程产生有效的作用时,应该选择这个方案。这种情况一般发生在系统安装之后,业务过程已经改变,新的业务过程不再依赖于遗留系统。

    2.不再大幅修改系统仅保持常规维护。当一个系统仍然有存在的必要,系统运行相当平稳,而且用户没有提出太多对系统变更的要求时,应该选择这个方案。

    3.对系统进行再工程以改善其可维护性。当系统质量由于经常性的变更已经下降,而且仍然需要做经常性的变更时,应该选择这个方案。这个过程应当包括开发新的构件接口,从而使最初的系统能和其他的新系统协同工作。

    4.用一个新的系统代替整个或部分系统。当其他因素(例如,新的硬件已经使用旧系统无法继续运行,或者有现成的产品可以使用)使新系统的开发成本非常合理时,就应做出此种选择。在很多情况下,可以采用演化替换策略,用现有的系统替换主要的系统构件,并且在可能的情况下继续使用其他构件。

    在评估一个遗留系统的时候,必须同时从业务和技术两个视角来看待它。从业务的视角看,必须对该系统的业务价值做出评估。从技术的视角看,必须对应用软件、系统支持软件和硬件质量做出评估。根据这两个方面得到的评估结果就能决定对该遗留系统采取何种策略了。一般地,有以下4种类型的系统:

    1.低质量、低业务价值系统。保持这些系统继续运转的费用很高、回 率很低。这类系统是抛弃的候选对象。

    2.低质量、高业务价值系统。这些系统正在为业务做出重要贡献,不能抛弃。不过,其低质量意味着运行的成本很高,所以这类系统有待于转换或者以合适的系统替代。

    3.高质量、低业务价值系统。这些系统对业务的贡献很小,但是维护费用较低。不值得冒险去替换这种系统,可以继续进行一般的系统维护,也可以抛弃。

    4.高质量、高业务价值系统。这种系统必须保持运转,其高质量说明无须对其投资进行转换或更换。正常的系统维护应该继续进行。

    为了评估一个系统的业务价值,我们必须假设是系统的所有者,比如最终用户和他们的经理,对系统提出以下4个方面的问题。

    1.系统的使用。如果系统只是偶尔被使用或被很少一部分人使用,那么它们含有较低的业务价值。遗留系统可能是为满足某些业务需要而开发的,但现在业务改变了,或者有其他更有效的方法满足要求。然而,一定要注意不经常使用但却重要的系统。

    2.支持的业务流程。当引入一个系统时,我们就要设计业务流程来开发系统的能力。如果遗留系统难以改变,那么改变这些业务流程就是不可能的。但是,当环境变化时,原来的业务流程可能会变得不可用。因而,由于不能引入新的流程,很可能使一个系统的业务价值降低。

    3.系统的可靠性。系统可靠性不仅仅是一个技术问题,也是一个业务问题。如果系统不可靠,而且问题直接影响商业用户,或者让业务处理中的人从别的任务转过来解决这些问题,那么系统的业务价值较低。

    4.系统的输出。这里的关键问题是系统输出对于成功的业务功能的重要性。如果业务依赖于这些输出,那么系统就含有高的业务价值。相反,如果这些输出可以用某种方法很容易地产生,或者系统产生的输出很少被使用,那么它的业务价值就很低。

    若从技术的角度来评估软件系统,需要考虑应用系统自身和系统的运行环境。运行环境包括硬件和相关的支持软件(编译器、开发环境等)。环境很重要,是因为很多系统变更是环境改变的结果,如硬件或操作系统的升级。

    在环境评估中需要考虑的因素如下表所示。注意这些因素并不是环境的所有技术特性,还要考虑硬件供应商和支持软件供应商的可依赖性。如果这些供应商不再从事此项业务,他们就不会再提供对系统维护的支持。

环境评估中所使用的因素
因素 问题
供应商稳定性 供应商还存在吗应商财务上是否稳定应商是否会继续存在果供应商不再从事相关业务,是否有其他人可以维护该系统
失效率 所 告的硬件失效率是否很高持软件是否崩溃并迫使系统重启
年限 硬件和软件已使用多少年件和软件越老就越落后。它们可能仍然正确运行,但转向一个更现代的系统会带来显著的经济和业务上的回
性能 系统的性能是否足够能问题对系统用户是否有显著的影响
支持需求 硬件和软件需要什么样的本地支持果这些支持成本很高,那么可能值的考虑下系统替换
维护成本 硬件维护和支持软件许可证的成本是什么较老的硬件的维护成本可能会比现代的系统更高。支持软件可能会有较高的年度许可成本
互操作性 该系统与其他系统的接口交互是否存在问题如,编译器是否可以与当前的操作系统版本一起使用

    如果可能的话,在对环境评估的过程中,应该给出系统的度量和系统的维护过程。有用的数据的例子包括:维护系统硬件和支持软件的花费,在某个确定时间内硬件缺陷发生的次数,对系统支持软件打补丁和修改发生的频度等。

    为了评估应用系统的技术质量,我们要评估一系列因素(见下表),这些因素主要关于系统的可靠性、维护系统的困难以及系统的文档建立。我们还要收集量化的系统数据来帮助我们对系统质量做出判断。质量评估中可能用到的数据有:

    1.请求系统变更的数量。系统变更容易造成系统结构的损坏,并为进一步变更增加了难度。这个数值越高,系统的质量也越低。

    2.用户界面数量。这对于基于表格的系统来说是一个重要的因素。在这种系统中,每一张表格都可以看作一个独立的用户界面。界面的数量越多,越容易发生界面的不一致和冗余。

    3.系统使用的数据量。使用的数据量(文件的数量、数据库的规模等)越大,就越有可能出现降低系统质量的数据不一致性。当数据经过长时间的积累,不可避免地会存在错误和不一致。清洗旧数据是一个非常昂贵和耗时的过程。

应用评估中所使用的因素
因素 问题
可理解性 理解当前系统的源代码有多难使用的控制结构有多复杂量的名字是否能反映它们的功能/td>
文档 可用的系统文档有哪些档是否齐全、一致并进行了及时更新/td>
数据 系统是否存在一个显式的数据模型件之间的数据在多大程度上存在重复统所使用的数据是否及时更新而且一致/td>
性能 应用的性能是否足够能问题对系统用户是否有显著的影响/td>
编程语言 开发该系统所使用的编程语言是否存在现代的编译器编程语言是否仍然被用于新系统开发/td>
配置管理 系统的所有部分的版本是否都由一个配置管理系统进行管理前系统中所使用的构件的版本是否存在一个明确的描述/td>
测试数据 系统的测试数据是否存在新特性被增加到系统中时,所执行的回归测试是否有记录/td>
人员技能 具有维护该应用的技能的人是否存在于该系统有经验的人是否存在/td>

    理论上说,应该根据客观的评估结果做出处理遗留系统的决定。然而在许多情况下,做出的决定并不是可观的,而是基于组织或政治上的考虑。

§9.3软件维护

    当软件交付后,软件维护就成为软件变更的一个常规过程。变更可以是一种更正代码错误的简单变更,也可以是更正设计错误的叫大范围的变更,还可以是对描述错误进行修正或提供新需求这样的重大改进。变更的实现时修改已有的系统构件以及在必要的地方添加新构件到系统中。有3种不同类型的软件维护:

    1.修复软件缺陷。通常改正代码错误费用相对较低,改正设计错误费用就高得多,因为要重写很多程序构件。需求错误的更正费用更改,因为必须对系统进行大量的重设计。

    2.使软件适应不同操作环境。在系统环境的某些方面发生改变时,需要进行这种类型的维护。环境上的改变包括硬件变化、操作系统平台的变化或其他的支持软件的变化。为了适应这些环境变化必须修改应用系统。

    3.增加或修改系统功能。当系统需求随着组织因素或业务改变而变更的时候,这种类型的维护就是必要的了。这时系统需要变更的范围通常要比其他类型的维护要大得多。

    【程序演化动力学】

    程序演化动力学是针对演化中的软件系统的研究,这一研究得出了所谓的Lehman定律,被认为适用于所有的大规模软件系统。这些定律中最重要的一些内容包括:

    1.一个程序如果要保持有用就必须持续变更;

    2.随着程序的不断变更,它的结构在退化;

    3.在一个程序的生命周期中,变化率大致不变,并且独立于可用的资源;

    4.在一个系统的每一个发布中的增量修改大致不变;

    5.新的功能必须加入系统中以增加用户的满意度。

    在实际过程中,这些不同类型的维护之间没有一个明确的界限。在使软件适应一个新环境的时候,可能需要增加新的功能来充分利用环境提供的服务。软件缺陷可能是因为系统在一种未预料的方式下使用才得以暴露,修正这类缺陷的最好方法是改变系统以适应它们的工作方式。

    尽管维护的不同类型得到了普遍认可,但有时可能会使用其他不同的分类名称。人们广泛使用的“纠正性维护”是指缺陷修补维护;“适应性维护”有时是指为适应新环境所做的维护,而有时又是指对新需求的适应性维护;“完善性维护”有时是指实现一个新需求来完善软件,而有时又是指保留系统的功能但改善结构和性能。

    下图显示了根据最新调查数据得出的维护费用的大致分布。该项研究将维护成本分布于1980—2005年的一些早期研究进行了比较,发现维护成本的分布在这30年间变化很小。虽然没有更多的最新数据,但这表明这种分布仍然很大程度上是正确的。修复系统缺陷不是最昂贵的维护活动。通过系统演化适应新环境以及新的或发生变化的需求通常会花费大部分的维护工作量。

    通常在系统投入使用之后增加功能,较之在开发期间实现相同的功能代价要高得多,主要原因如下有以下4点:

    1.团队稳定性。系统移交之后通常要解散团队,把人员分配到其他新项目中。负责系统维护的新团队或个人既不了解该系统,也不了解系统设计决策的背景,这样在对系统进行变更之前就要花费很多精力来理解现有系统。

    2.糟糕的开发实践。系统的维护合同一般是独立于系统开发合同的。维护合同是与另外的公司签署的,而不是与原开发者签署的。这个因素连同缺乏团队稳定性因素一起,使开发团队缺乏动力去写维护性好的软件。如果一个开发团队为减少工作量而走捷径,即使意味着以后软件的改动会更加困难,他们也认为值得去做。

    3.人员的技术水平。维护人员一般都缺乏经验,而且不熟悉应用领域。软件工程人员对维护活动没有什么好印象,通常认为维护不需要太多技术,不如做系统开发那么光彩,所以通常是分配最低级的职员去做。此外,旧的系统可能是用已经淘汰的程序语言写成的,维护人员可能没有多少使用这些语言开发的经验,必须经过学习方能胜任维护工作。

    4.程序年限和结构。随着程序不断的变更,其结构受到了破坏。结果是,随着程序年龄的增加,它们变得越来越不容易理解和变更。此外,许多遗留系统没有使用现代化的软件工程技术来开发。这些系统的结构在设计之初就没有规划好,而且系统开发通常只注重效率优化而很少考虑其易理解性。这些老系统的文档要么没有要么就是不完整,还有可能缺乏一致性。旧的系统也没有采用配置管理,因此在进行系统变更时,常常要在寻找系统构件的合适版本上浪费时间。

    【文档】

    系统文档可以通过为维护者提供关于系统的结构和组织的信息以及系统向用户提供的特征来帮助维护过程。虽然敏捷方法的支持者认为代码应该是首要的文档,更高层的设计模型以及关于依赖和约束的信息可以使理解代码以及修改代码变得更容易。

    前三个问题的存在是因为很多组织仍然把系统开发和维护看作独立的活动。维护被视为第二等的活动,而且没有动力为减少系统变更开销而投资。要想彻底解决这个问题,首先必须接受这样一个观点:系统很少有一个确定的生存周期,而是以某种形态在一个不确定的期限内连续使用。正如前面所说的那样,应当将系统看成是贯穿于它生命周期的在连续的开发过程中不断演化的。软件维护应该和新软件开发有同等的地位。

    第4个问题,即退化的系统结构问题,在某种程度上讲是最容易解决的问题。可以通过再工程技术来改善系统结构和可理解性。如果适当的话,可以通过体系结构的转换使系统适应新的硬件。重构能够提升系统代码的质量并且可以使之更加容易修改。

    原则上,投入力量设计和实现一个系统来降低未来变更的成本几乎总是合算的。在交付之后添加新功能是昂贵的,因为必须花时间学习系统并分析所提出的变更的影响。在开发过程中所做的对软件进行组织使之更容易理解和修改的工作可以降低演化成本。好的软件工程技术,例如精确的规格说明、测试现行的开发、面向对象开发、配置管理的使用都有助于降低维护成本。

    这些为通过提高系统可维护性上的投资来获得整个生命周期的成本节省提供了原则上的论据,然而不幸的是这些论据无法通过真实数据来证实。收集数据很昂贵,数据的价值难以判断。因此,绝大多数公司都认为收集和分析软件工程数据不值得。

    实际上,大多数企业都不太愿意在软件开发上花费更多的精力来降低长期维护成本。他们之所以不情愿主要是因为以下两个原因:

    1.公司制定季度或年度支出计划,并鼓励管理人员减少短期成本。投资可维护性会导致短期成本上涨,这部分是可衡量的。然而,与此同时,长期收益却很难衡量,所以企业不愿意将钱花在未来回 未知的事情上。

    2.开发人员通常不负责维护他们所开发的系统。因此,他们很少想到通过一些额外的工作来降低维护成本,因为他们不会从中获益。

    解决这个问题的唯一方法是集成开发和维护,从而使最初的开发团队在整个软件生命周期中始终对软件负责。

    9.3.1维护预测

    维护预测关注评估软件系统可能需要的变更,并识别系统中变更的代价有可能最高的部分。如果理解这些,那么就可以针对最有可能发生变更的软件构件进行特别的设计,从而使其具有更好的适应性。还可以投入力量去改进这些构件,以减少其生命周期维护成本。同样,应当试着去估计在给定时间内的系统的维护成本。下图说明了对这些不同方面的预测和相关问题。

    预测变更请求的数量需要了解系统和外部环境之间的关系。许多系统与外部环境之间存在着复杂的关系,对环境所做的改变不可避免地导致系统变更的发生。要对系统和系统的环境之间的关系做出判断,应该评估以下几点。

    1.系统接口的数量和复杂性。接口越多、越复杂,接口变更请求就越有可能作为新的需求被提出来。

    2.自身具有易变性的系统需求的数量。那些反映组织政策和流程的需求比那些基于稳定的领域特性的需求更容易变动。

    3.系统被使用时所处的业务过程。业务过程在演化的时候,就会产生系统变更请求。使用系统的业务过程越多,要求系统变更的请求就会越多。

    很多年以来,研究人员一直在研究程序复杂性和可维护性之间的关系。这些研究结论都在意料之中:系统或构件越复杂,其维护费用就越高。复杂性度量在识别那些维护费用特别高的个别程序构件时特别有用。因此,用比较简单的构件去代替特别复杂的系统构件是划算的。

    当系统已经投入服务的时候,可以使用过程数据来帮助预测可维护性。对可维护性评估有用的过程度量包括以下4个:

    1.请求纠正性维护的数量。如果失败 告的数量在增加,这可能暗示着在维护过程期间有更多的错误引入程序之中了,这样可能会导致系统可维护性的下降。

    2.影响分析所需的平均时间。它反映了受到变更请求影响的程序构件数量。如果这个时间在增加,就暗示着越来越多的构件受到影响,可维护性正在下降。

    3.实现一个变更请求的平均时间。这不同于影响分析所需的时间,尽管它们之间可能存在关联性。它所涉及的活动是对系统及其文档进行变更,而不是只评估哪些构件受到影响。如果实现变更的时间在增加,这可能预示着可维护性在下降。

    4.突出的变更请求的数量。如果这种变更请求数量随着时间在增加,可能意味着可维护性在下降。

    我们根据变更请求的预测信息和系统可维护性的预测信息来预测维护费用。多数管理者是将这些信息和自己的直觉、经验相结合来进行成本估计的。成本估计的COCOMO 2模型指出:软件维护工作量是基于现有代码的工作量和开发新代码的工作量来估计的。

    9.3.2软件再工程

    系统演化过程包括去理解要变更的程序,然后去实现这些变更。但是,对于很多系统,特别是遗留的老系统,它们是很难理解和进行变更的。这些程序可能最初牺牲了一些可理解性来换取在性能上或空间利用上的改善。此外,随着时间的推移,最初的程序结构经过一系列的变更后已被破坏了。

    为了使得遗留系统的维护问题变得更简单,可以再工程这些系统以增强它们的结构性和可理解性。再工程包括对系统重新建立文档、重构体系结构、用一种更先进的程序设计语言转换系统、修改和更新系统的数据结构和系统的数据取值。一般来讲,软件的功能不会改变,也应当避免对系统体系结构的大的改动。

    再工程相对于直接替换系统来说,有两个重要的优势:

    1.较小的风险。对某个关键业务软件的重新开发是要冒很高风险的。系统描述中会发生错误,而且开发过程中也会出现种种问题。在引入新软件上时间的拖延将意味着商业上的损失招致额外的花费。

    2.较低的成本。较之重新开发一个软件所用的成本,再工程的成本要小得多。即使运用先进的软件技术,重新实现的花费可能还是要比再工程的花费多。

    下图是一个再工程过程的通用模型。过程的输入是一个遗留程序,输出是同一个程序的一个已改进和重新构造的版本。这个再工程过程中的活动如下:

    1.源代码转换。使用转换工具,将程序从旧的程序设计语言转换到相同语言的一个比较新的版本或另一种语言。

    2.逆向工程。对程序进行分析,并从中抽取信息来记录它的组织结构和功能。这个过程通常是全自动完成的。

    3.程序结构改进。对程序的控制结构进行分析和修改,使它更容易阅读和理解。

    4.程序模块化。程序的相关部分被收集在一起,在一定程度上消除冗余。在某些情况下,这个阶段可能包括体系结构的重构(例如,一个系统本来使用几个不同的数据存储器,结果被要求使用一个单独的存储器)。

    5.数据再工程。改变程序处理的数据以反映程序变更。这可能意味着重新定义数据库模式和将已存在的数据库向新的结构转变。需要经常清理数据,包括查找和改正错误、删除冗余记录,等等。

    程序再工程可能不需要上图中显示的所有步骤,如果仍使用应用程序的编程语言,源代码就没有必要做转换;如果再工程完全依赖于自动化工具,那么通过逆向工程来恢复文档就没有必要了。数据再工程只有在系统再工程期间程序中数据结构发生了改变时才是需要的。

    为了使再工程系统和新软件互操作,我们可能需要开发适配器构件。这样就隐藏了软件系统的最初接口,呈现出新的、较好的结构化接口,可以被其他构件使用。对于开发大型的可复用构件来说,遗留系统的封装是一项很重要的技术。

    再工程的成本很显然依赖于所做的工作的程度。下图给出了再工程所可能使用方法的一个谱系。成本从左到右逐渐增长,所以源代码转换是便宜的选项,而再工程加上一部分体系结构迁移是费用最高的选项。

    软件再工程的缺点是,系统经过再工程后能改善的范围受到一定的限制。举例来说,它不可能把面向过程的系统转换成面向对象的系统;主要体系结构的变更或对系统数据管理的重新组织不能自动地执行,因此需要额外的高成本;虽然再工程能改善可维护性,但经过再工程的系统不可能像用现代软件工程方法开发的新系统一样好维护。

    9.3.3软件重构

    重构(refactoring)是提升程序以减缓其由于更改而退化的过程。它意味着通过修改程序来改进程序结构性,降低程序复杂性,让程序变得更加易于理解。重构有时被认为局限于面向对象的开发,但是其原理可以被任何开发方法所使用。当重构一个程序时,不应该增加其功能,而应该注重程序的改进。因此,可以把重构看成是“预防性维护”,以此来减少未来变更产生的问题。

    重构是类似于极限编程这样的敏捷方法的一个固有部分,因为这些方法都是应对程序变更的。因此,程序的质量容易退化得很快。所以敏捷开发者经常重构它们的程序来避免这样的退化。对于敏捷方法中对回归测试的强调,降低了由于重构引进新错误的风险。引入的任何错误都是可检测的,因为之前成功的测试这时会失败。然而,重构不依赖于其他的“敏捷活动”,并且能够被任何方法用于开发。

    尽管再工程和重构都是要将软件变得更加容易理解和变更,但它们并不是同一回事。再工程发生在系统已经维护了一段时间并且维护费用不断上升的情况下,通过使用自动化工具来处理并再工程一个遗留系统,产生一个更具可维护性的新系统。重构是一个连续不断的改进过程,它贯穿于开发和演化的整个过程。重构是要避免导致成本上升和维护困难的结构以及代码的退化问题。

    有的研究表示,存在一些固定模式的情况,其中程序的代码能够通过重构被改进的情况包括如下:

    1.冗余代码。在程序的不同地方相似的代码可能重复出现很多次。这种情况下可以删除它并用一个方法或要求的功能去实现。

    2.长方法。如果方法太长了,那么这个方法应当被重新设计成几个较短的方法。

    3.选择语句。这种情况常常牵扯到重复,因为选择语句switch依靠的是同一个值的不同类型。选择语句可能分散在程序的各个地方。在面向对象语言中,常常可以通过多态性来实现同一个事情。

    4.数据聚集。当同样的一组数据项(类中的域,方法中的参数)在程序的不同地方重复出现时,数据聚集就出现了。这通常可以通过用一个对象封装所有数据来解决。

    5.假设的一般性。即开发者为了以后万一使用到在程序中包含了一般性。这通常可以简单地删除掉。

    重构在程序开发期间实现,是一项有效降低程序长期维护成本的途径。然而,在需要维护一个其结构已经明显退化的程序时,仅仅通过代码重构是远远不够的。可能还要考虑对设计的重构,这可能是一个花费更高和更加困难的问题。设计重构包括识别相关设计模式,并用实现这些设计模式的代码替换原先的代码。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年5月11日
下一篇 2020年5月11日

相关推荐