12软件重构技术综述Word下载.docx
- 文档编号:19522092
- 上传时间:2023-01-07
- 格式:DOCX
- 页数:44
- 大小:328.28KB
12软件重构技术综述Word下载.docx
《12软件重构技术综述Word下载.docx》由会员分享,可在线阅读,更多相关《12软件重构技术综述Word下载.docx(44页珍藏版)》请在冰豆网上搜索。
于是,一个程序员增加的代码变成了下一个程序员咒骂的对象。
系统变得越来越难以理解,维护越来越困难、越来越昂贵。
系统变成了一个十足的大泥潭。
每个人都不愿意看到这种情况,但奇怪的是,这样的情形却一次又一次地在现实中不断地出现。
这是长期以来一直困扰人们而必须加以解决的问题。
实际上,这就是在软件维护研究中提出的软件熵(softwareentropy)的概念。
软件熵是软件的结构随着时间而变坏的现象。
FredBrooks说:
所有维修都倾向于破坏结构,增加了系统的熵和凌乱。
在修理原始设计缺陷上所花的时间越来越少,越来越多的时间是花在修理由早期修理所引入的错误上。
随着时间的流逝,系统变得越来越混乱。
迟早修理变得不可能而停止下来[Bro75]。
解决这个问题的最好方法当然是不让它发生,即使发生了,我们也应该有办法来对付。
由此引发了软件重构技术(softwarerefactoringtechnology)的研究。
如果编码时小心,代码的结构和设计应没有机会变坏。
每次在修改或者增加代码之前,通过查看代码,如果代码具有良好形状和味道(codesmell),那么添加功能就应该没问题;
如果理解代码要花很多时间,代码应该先被重构(重新组织修改)以获得易维护属性,然后才能够容易扩展代码。
当然,要阻止代码的腐化,人们也需要付出额外的代价。
重构研究的动机是由某些有关的研究所激发的。
这些研究主要包括软件重用、软件维护、和软件的重新组织(SoftwareRestructuring)。
(1)软件重用(SoftwareReuse)
为了降低开发软件的高成本,软件重用的研究是为了使一个系统开发的知识再次容易地用于另一个软件系统的开发。
然而,可重用软件经常需要很多设计迭代。
使软件更容易改变将使设计迭代更简单。
这样的软件将更可重用。
抽象、封装、继承、多态和模块性这样的面向对象程序设计特性给软件再利用提供了基础结构。
在Smalltalk和其它的一些面向对象语言中提供了这些特性的结合,以鼓励再利用现有的代码,而不是从头开始编码。
这样,通过添加新类或者在现存类上添加操作能对软件作出某些改变,而使软件的大部分保持不变。
然而,大部分的改变涉及类似改变类间关系,移动类间的变量和函数这样的结构上的变化。
用手工进行这些改变是费时、困难并易于出错的。
除代码级再利用之外,设计级的再利用也是研究的内容。
而且,从长期的观点来看,人们认识到设计级的再利用更为重要。
面向对象应用框架(framework)是这种研究努力的结果。
框架是抽象和具体类的集合。
从而,能够添加新的子类对它进行重定义。
因此,框架支持抽象级,并允许部分的规范说明。
不过,好的框架需要多次的设计迭代,涉及很多结构上的变化。
(2)软件维护(SoftwareMaintenance)
软件重用与软件维护紧密相关。
维护是软件生产所有方面中最为困难的。
主要的理由在于维护包容了软件过程所有其他阶段的各个方面内容。
在软件生命周期中,在维护上所花的时间比任何其它阶段都更多。
实际上,现行软件的维护工作量能占到全部开发工作量的60%以上。
软件维护经常需要重新组织软件。
(3)软件重新组织(SoftwareRestructuring)
Arnold把软件重新组织定义为“使软件更容易理解、更容易改动、或者以后修改时更少出错,而对软件的更改[Arn86]”。
不适当的设计方法学,缺乏开发和维护标准等诸如此类的很多因素都能导致拙劣的软件结构。
很多软件重组方法集中于诸如用结构注入代码使程序中的控制流更为明显这样的代码变化。
可是,只存在一些不对代码进行更改的方法。
例如,人们能够在软件再工程期间从代码和现有的文档出发重新创建软件的结构。
2重构的基本概念
2.1重构的定义
重构的定义出现在各类出版物中,每个人似乎都有自己的重构的定义。
在很多的定义中,最先对重构进行理论研究的RalohJohnson给出的定义[Wiki,ReFactor]显然具有说服力:
重构是以各种方式对一对象设计进行重新安排使之更灵活并且/或者可重用的过程。
效率和可维护性可能是进行重构最重要的理由。
MartinFowle给出更一般的定义[Fow99],他把重构定义为名词形式和动词形式两部分:
重构(Refactoring,名词):
是对软件的内部结构所作的一种改变,这种改变在可观察行为(Observablebehaviour)不变的条件下使软件更容易理解,而且修改更廉价。
重构(Refactor,动词):
应用一系列不改变软件可观察行为的重构操作对软件进行重新组织(restructure)。
这些定义中最重要的方面是不改变软件系统的可观察行为,并且改变软件结构是朝着更好的设计和更能理解,而且可重用的方向进行。
重构提供一种以更有效和受控方式清理代码的技术[Fow99]。
MartinFowler的名词形式就是说重构是对软件内部结构的改变,这种改变的前提是不能改变程序的可观察的行为,这种改变的目的就是为了让它更容易理解,更容易被修改。
动词形式则突出重构是一种软件重构行为,这种重构的方法就是应用一系列的重构操作。
软件结构可以因为各种各样的原因而被改变,如进行打印美化、性能优化等等,但只有出于可理解性、可修改、可维护目的的改变才是重构。
这种改变必须保持可观察的行为,按照Martin的话来说,就是重构之前软件实现什么功能,之后照样实现什么功能。
任何用户,不管是终端用户还是其他的程序员,都不需要知道某些内部的事情发生了变化。
2.2重构的原则
2.2.1一个时刻只戴一顶帽子
KentBeck把重构和增加功能比喻为两顶帽子(TwoHats)。
他说,如果你使用重构开发软件,你把开发时间分给两种不同的活动:
增加功能和重构。
增加功能时,你不应该改变任何已经存在的代码,你只是在增加新功能。
这个时候,你增加新的测试,然后让这些新测试能够通过。
当你换一顶帽子重构时,你要记住你不应该增加任何新功能,你只是在重构代码。
你不会增加新的测试(除非发现以前漏掉了一个)。
只有当你的重构改变了一个原先代码的接口时才改变某些测试。
在一个软件的开发过程中,你可能频繁地交换这两顶帽子。
你开始增加一个新功能,这时你认识到,如果原来的代码结构更好一点,新功能就能够更方便地加入。
因此,你脱下增加功能的帽子,换上重构的帽子。
一会儿,代码结构变好了,你脱下重构的帽子,戴上增加功能的帽子。
增加了新功能以后,你可能发现你的代码使得程序的结构难以理解,这时你又戴上重构帽子。
关于两顶帽子交换的故事不断地发生在日常开发中,但是不管你戴着哪一顶帽子,一定要记住一个时刻只戴一顶帽子,做一件事情。
2.2.2可观察行为保持
保持代码的可观察行为不变称为重构的安全性。
重构工具用半形式化的理论证明来保证重构的安全性。
但是,要从理论上完全证明系统的可观察行为保持不变,虽然不是说不可能,也是十分困难的。
工具也有自己的缺陷。
首先,目前对于重构的理论研究并非十分成熟,某些曾经被证明安全的重构最近被发现在特定的场合下并不安全。
其次,目前的工具不能很好地支持“非正式”的重构操作,如果你发现一种新的重构技巧,工具不能立即让这种重构为你所用。
自动化的测试是检验重构安全性非常方便而且有效的方法。
虽然我们不能穷尽整个系统中所有的测试,但如果在重构之前成功的测试现在失败了,我们就会知道刚刚做的重构破坏了系统的可观察行为。
自动化测试能够在程序员不进行人工干预的情况下自动检测到这样的行为破坏。
2.2.3小步前进
重构的另一个原则是小步前进,即每一步总是做很少的工作,每做少量修改,就进行测试,保证重构的程序是安全的。
如果你一次做了太多的修改,那么就有可能介入很多的错误,代码将难以调试。
如果这时你发现修改并不正确,要想返回到原来的状态也十分困难。
这些细小的步骤包括:
确定需要重构的位置,编写并运行单元测试,找到合适的重构并进行实施,运行单元测试,修改单元测试,运行所有的单元测试和功能测试等。
如果按照小步前进的方式去做重构,那么出错的机会可能就很小。
正如KentBeck所说:
“我不是一个伟大的程序员,但我是一个具有伟大习惯的好程序员”。
要求使用小步骤渐进地重构并不完全出于对实践易行的考虑。
RalphJohnson在伊利诺斯州立大学领导的一个研究小组是重构理论的引导者和最重要的理论研究团体。
其中WilliamOpdyke是公认的第一位重构正式提出者。
在他的博士论文[Opd92]中,Opdyke描述他对重构层次的看法:
通常,人们要么在增加系统特性这样一个高层次上,要么在被改变的代码行这样一种低层次上来看待软件的变化。
重构是一种重新组织计划,它支持在一个中间层次上的改变。
例如,重构把一个类的成员函数移到另外一个类……。
为了实现在这样的中间层次上的操作,Opdyke提出了原子(atomic)重构的概念,他指出:
下面列出的支持重构的最终分类是原子的;
也就是说,它们是最原始级别的重构操作。
原子重构创建、删除、改变及移动实体……高级重构通过这26个低级(原子)重构得到支持。
论文中,Opdyke首先证明在一定的前提之下,这些原子重构将不会改变程序的可观察行为。
更高级重构的可观察行为保持可以通过将它们分解为这些原子重构加以证明。
Opdyke也证明了他所提出的高级重构如何在每一步原子重构之后都符合后续原子重构所需要的前提。
小步前进使得对每一步重构进行证明成为可能,最终通过组合这些证明,可以从更高层次上来证明这些重构的安全性和正确性。
重构工具依赖于这些理论研究来进行重构。
如果每个人能够按照这样的一小步一小步的方法进行重构,那么极有希望将他的重构正确地记录下来,为整个面向对象社团所用。
同时,在理论上的正确证明可以促使重构工具得到进一步的发展。
也许你会认为,随着工具的发展,程序员将变成重构机器人。
这样的看法是不正确的。
虽然使用一个重构工具能够避免使用手工方式可能介入的各种各样错误,减少编译、测试和代码复审(codereview)。
但是正如Smalltalk重构浏览器(RefactoryBrowser)开发者DonRoberts所说,重构工具不打算用来代替程序员,程序员需要自己来决定什么地方需要重构,做什么样的重构。
而在这一点上,经验是不可代替的。
2.2.4DONROBERTS的3次规则(TheRuleofThree)
DonRoberts提出的3次规则[Fow99]揭示了开展重构的规律:
第一次做某件事,你直接完成就好。
第二次做某件事,当你看到重复,有些退缩,但无论如何你重复就是了。
第三次做类似的事,你进行重构。
2.3重构的目标和本质
从概念上讲重构的目标是对软件系统的设计进行重新组织,使系统满足某些通用设计的准则,并具有容易扩展系统功能的结构或者形状。
在重构应用与实践中,作为面向对象社团最重要贡献之一的设计模式(designpattern)为重构提供了一个明确的目标。
重构的实质是在保持可观察行为不变的前提下,为提高软件的可理解性,可扩展性和可重用性而对软件进行的修改。
对软件进行修改的基础是分析,因而,软件重构的本质是分析。
WaldenMathews在分析开发过程与重构的关系上指出:
当你进行重构的时候,你就在进行分析。
做重构的正确时间不能来自某些过程手册或方法论,而是当你看到能够更有力和更简单地表达事情的时候。
项目阶段和方法是不重要的。
人、以及他们的技能和项目的上下文(context)才是重要的[Wiki,RefactorMercilessy,WaldenMathews]。
2.4重构的组成与步骤
重构由许多小的步骤组成。
当一次对系统作了很多改变时,在此过程中也极有可能引入许多错误。
但产生这些错误的时间和地点是不可再现的。
如果以小步前进的方式实现对系统的改变,并在每一步后运行测试的话,错误就有可能在它引入系统后的测试中立即表现出来。
然后对每步的结果进行检查,如果有问题,可撤消此步所作的改变。
在复原之后,可以采取更小的步骤前进。
重构软件系统采取的步骤如下:
1.确定需要重构的位置。
可以通过理解,扩展,或者重新组织系统来发现问题,或者通过查看实际代码中的代码味道(codesmell)来确定位置,或者通过某些代码分析工具。
2.如果所考虑的代码的单元测试存在的话,就运行该单元测试看看能否正确的完成。
否则,编写所有必要的单元测试并运行它们。
3.通过思考或者查看重构分类目录,找出能够明显被应用的重构操作。
4.遵循小步前进的原则实现重构操作。
5.在每步间运行测试以保证行为未被改变。
6.如有必要,对作过改变的接口修改测试代码。
7.当重构操作被成功地用于重新组织代码,再次运行测试,集成并运行全部的单元测试和功能测试。
重构没有创造性的说法是错误的。
有趣的部分在于发现系统中需要重构的结构不好的部分。
因为缺乏良好设计的度量标准,重构是必须发展的实用技能。
2.5重构系统的基本过程
目前的程序设计实践一般涉及对源码形式的程序进行维护和更新。
为了将重构集成到这样的编程环境中,在重构前首先必须将程序源码转换成一种抽象语法树的表示,并且重构之后必须从抽象语法树的表示再次生成更新后的程序源代码。
图1表示了重构过程的各个阶段。
在重构系统内,扫描器创建程序源码的语法树表示,扫描树被传给重构程序,重构程序首先分析语法树,并添加类型和交叉引用信息,重构程序然后根据来自用户的命令对内部表示进行处理,最后调用美化打印程序重新生成并打印重构后的代码。
图1:
软件重构过程
2.6重构的编目
除了工具能够有效地支持重构以外,对重构操作进行编目是另一个成功的方法。
重构目录能够帮助开发者以最不困难的方式进行重构的应用。
重构已经被有经验的面向对象程序员成功地使用了10多年。
这些程序员也把他们重构经验反馈给了面向对象社团。
重构的编目就是对这些经验的整理和分类组织。
这对于重构技术的发展以及推广应用具有积极意义。
MartinFowler在他的书[Fow99]中给出了他收集整理和从长期工作实践经验提炼出来的一个重构操作分类目录,详细描述了70多种重构方法。
向开发者提供这样一个尽管不完整,但非常有实用价值的分类目录成为他写书的主要意图。
由于重构技术处于不断发展之中,一方面对于目录中所列的重构操作需要不断地更新和完善,另一方面,对新发现的重构操作也需要不断地纳入其中,使重构目录得到进一步扩充。
因此重构目录始终处于不断完善和发展的动态变化之中。
维护并向开发者提供这样一个能够体现最新成果的重构目录是更有意义和价值的工作。
MartinFowler意识到这一点,因此他在一个有关重构技术的主页[]上维护了一个联机目录,上面放有最近扩展的一些重构方法,同时他也鼓励任何想对重构收集作出贡献的人维护和扩展这个目录。
这个目录不仅用名字列出了目前已被使用以及很好实验过的重构操作,而且还介绍了一种标准表示法。
这种标准表示法使任何人都能容易地获得重构操作的主要思想,后面跟有实例,以及一步步运用重构而不会引入错误的用法。
不幸的是标准表示法的思想在用于设计模式时不是那么成功。
任何想对设计模式作出贡献的人也在考虑将设计模式记载下来的更好的表示方法,因为由“四人帮”(gangoffour)最先提出的表示法[GOF99]似乎是不够的。
重构表示法由下列元素构成:
●名字
建立一个很像设计模式名那样的词汇
●简要概述
关于该重构操作何时需要用以及它做什么的简单说明,包括问题说明,使用的反映,以及最可能是以代码形式(包括重构前和重构后的代码)或者一种UML图的形式表示的框架
●动机
描述为什么应该使用的原因以及何时不应使用
●用法
关于如何一步步执行重构操作的说明,以及使其容易被引用的简短注释
●例子
显示该重构操作的一个简单应用
2.6重构的优点
重构增加了具有某种缺点的程序的价值。
KentBeck指出:
难读的程序难修改。
具有重复逻辑的程序难修改。
带有复杂条件逻辑的程序难修改。
重构虽然需要更多的“额外工作”,但是它给我们带来的各种好处显然值得我们做出这样的努力[Fow99]。
2.6.1简化测试
一个好的重构实现能够减少对新设计的测试量。
因为重构的每一步都保持可观察的行为,也就是保持系统的所有单元测试都能顺利通过。
所以只有发生改变的代码需要测试。
这种增量测试使得所有的后续测试都建立在坚实的基础之上,整个系统测试的复杂性大大降低。
2.6.2增进软件可理解性
程序的最终目的是为了指引计算机完成人们需要它完成的事情。
但是,要完成这个目标并非想象的那么容易。
程序编写是人的活动,人首先要理解才能行动。
所以,源代码的另一个作用就是用于交流的工具。
其他人可能会在几个月之后修改代码,如果连理解代码都做不到,又如何完成所需的修改呢?
我们通常会忘掉源代码的这种用处,尽管它可能是源代码更重要的用处。
不然,我们为什么发展高级语言、面向对象语言,为什么我们不直接使用汇编语言甚至是机器语言来编写程序?
难道我们真的在意计算机多花了几个CPU周期去完成一件事?
如果一个人能够理解我们的代码,他可能只需要一天的时间完成一个增加功能的任务,而如果他不理解我们的代码,可能需要花上一个礼拜或更长的时间。
这里的问题是,我们在编写代码的时候不但需要考虑计算机CPU的想法,更要把以后的开发者放在心上。
除非,你写代码的唯一目的就是把它丢掉。
除非你不想让任何别的开发人员用到这段代码,包括你自己。
因为你不可能记得所有你写过的代码,如果你经常回过头去看一下自己的代码,你就会体会到代码的可理解是如何重要。
不仅仅只是机器,还有许多人与系统的程序代码打交道。
对他们最重要的是能够容易地理解系统。
除了文档和设计图纸(在软件生命期中常常被丢失),源代码是软件存在的最重要的文档形式。
你可能认为,反映代码的意图应当是注释和文档的责任。
假设你写好了一段程序,给他加上注释,来说明你这段代码完成了什么。
每次代码发生改变,你都需要修改注释。
而其实代码本身应当足以说明这个问题。
如果代码不能反映自己的意图,即使是再多的注释也不足以让你理解代码的所有机制,除非你把代码的每一句话都加上注释。
甚至,这种重复的责任使得一旦注释和代码发生不一致,它反而会阻碍你对代码的理解。
而任何一个程序员都不愿意写太多的注释和文档。
如果注解仅仅阐述代码所作的事情,他们就是无用的。
因为所作的事情已经通过代码表达出来了。
如果代码不传达它的行为,就需要对它进行重构(方法重命名,类重命名,提取方法,加固条件表达式,...)。
软件的代码应该以易读的方式来编写:
它能解释它的所作所为,注解应该用于描述为何要这样做(意图)。
任何结构良好而且很好命名的程序并不需要很多注解。
所以MartinFowler说:
当你感到有必要写注解时,首先尝试重构代码,使任何注解变成是多余的[Fow99]。
重构可以使得你的代码更容易理解。
原因在于重构支持更小的类、更短的方法、更少的局部变量、更小的系统耦合,重构要求你更加小心自己的命名机制,让名字反映出你的意图。
如果哪一块代码太复杂以至于难于理解,你都需要对它进行重构。
MartinFowler同时指出,重构不但能够增加别人对你的代码理解,而且是一种非常好的理解别人代码,学习别人代码的方法。
即使缺乏任何其它的文档,清晰的结构、依然可见的设计,还有适当的命名能使人们容易理解整个系统。
通常,当你拿到一大堆代码时,你可能会觉得一片茫然,不知道从何处开始。
当有必要理解不熟悉的代码时,重构就是一种方法。
学习别人代码的最好方法就是对代码进行重构。
如果你发现自己不能理解一段代码,重构它,命名一些有意义的名字。
重构使得你对代码的理解不仅仅停留在脑袋中,而是看到代码确实按照你的理解在发生变化。
如果你的重构操作改变了系统的行为,那么说明你的理解还有问题。
你必须返回重来。
人们能够对有着特别理解的一段代码进行变换(重构)以反映个人的理解。
当变换后的代码在测试时无错误地得到执行,个人的理解就得到确认。
2.6.3改善软件设计
大部分的软件工程师把程序设计看成是必要的,但不是如此重要的一种低级活动,他们认为代码仅仅是设计的附属物。
程序设计在实现阶段完成。
然而正是代码,而不是你脑袋里或纸上的设计,真正驱动计算机完成你想做的事情。
而绝大多数的故障也产生于编码阶段。
在当时或者以后找出这些故障被事实证明是非常昂贵的。
很多方法学强调把注意力放在分析和设计阶段,把实现定义为按照设计进行编码,以努力防止产生故障。
这些方法学认为通过分析和设计的严格化就能产生更高质量的软件。
然而,这仅仅是理论,实际上是不可能的。
正如BrainFoote和JosephYoder在《BigBallofMUD》一文中指出,虽然许多作者提出了很多理想上非常完美的体系结构,如Layer、Pipeline等等。
但在实践中,几乎从来都不能看到这么结构清晰的系统。
这一方面是由于分析和设计一个领域的应用程序需要对该领域丰富的知识,而这种知识不是在一开始都能获得的。
我们通常需要在实践反馈的过程中才能一步步加深自己对该领域的理解。
因而,我们一开始的设计可能并不能正确反映系统的内在本质,所以也不可能在代码中得到很好的反映。
大多数软件系统的客户并不确切地知道他们需要的是什么,在项目开始初期给出的需求通常是模糊和不完整的。
随着项目的继续,需求被细化、改变或者取
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 12 软件 技术 综述