??xml version="1.0" encoding="utf-8" standalone="yes"?>
介绍
代码太容易变坏。代码L向于有更大的类、更长的Ҏ、更多的开兌句和更深的条件嵌套。重复代码随处可见,特别是那些初看相似细看又不同的代码泛滥于整个pȝQ条件表辑ּQ@环结构、集合枚?#8230;.信息被共享于pȝ一些关pȝ的l成部分之间Q通常Q这使得pȝ中几乎所有的重要信息都变成全局或者重复。你Ҏ不能看到q种代码q有什么良好的设计?如果有的话,也已l不可L识了?
q样的代码难以理解,更不要说对它加以修改。如果你兛_pȝ体系l构、设计,或者是一个好E序Q你的第一反应是拒绝工作于这L代码。你会说Q?q么烂的代码Q让我修改,q不如重写?然而,你不大可能完全重写已l能够甚x正在q作的系l,你不能保证新的系l能够实现全部的原有功能。更何况Q你不是生活在真I,q有更多的投资、交付、竞争压力?/p>
于是你用一Uquick-and-dirty的方法,如果pȝ有问题,那么q接找到这个问题,便当C改它。如果要增加一个新功能Q你会从原来的系l中扑ֈ一块相q的代码Q拷出来Q作些修攏V对于原来的pȝQ你惻I既然我不能重头写q,而且它们已经在运作,让它d。然后,你增加的代码变成了下一个程序员咒骂的对象。系l越来越难以理解Q维护越来越困难、越来越昂贵。系l变成了一个十的大惔球?/p>
q种情况是每一个h都不愿意到的,但是奇怪的是,q样的情景一ơ又一ơ出现在大多Ch的编E生涯中。这是因为我们不知道该如何解冟?/p>
解决q个问题的最好办法当然是让它不要发生。然而,要阻止代码的腐化Q你需要付出额外的代h。每ơ在修改或增加代码之前,你都要看一看手上的q些代码。如果它有很好的味道Q那么你应该能够很方便地加入新的功能。如果你需要花很长的时间去理解原来的代码,花更长的旉d加和修改代码。那么,先放下手里的z,让我们来做Refactoring?/p>
什么是RefactoringQ?/strong>
每个Z乎都有自qRefactoring的定义,管他们讲的是同一件事情。在那么多的定义中,最先对Refactoringq行理论研究的Raloh Johnson的话昄更有说服力:
Refactoring是用各U手D重新整理一个对象设计的q程Q目的是Z让设计更加灵zdƈ?或者更可重用。你可能有几个理由来做这件事情,其中效率和可l护性可能是最重要的原因?/p>
Martin Fowler[Fowler]把Refactoring定义Z部分Q一部分为名词Ş式:RefactoringQ名词)Q在不改变可观察行ؓ的前提下Q对软g内部l构的改变,目的是它更易于理解q且能够更廉价地q行改变。另一部分则是动词形式QRefactorQ动词)Q通过应用一pd不改变Y件可观察行ؓ的refactoring来重构一个Y件?/p>
Martin Fowler的名词Ş式就是说Refactoring是对软g内部l构的改变,q种改变的前提是不能改变E序的可观察的行为,q种改变的目的就是ؓ了让它更Ҏ理解Q更Ҏ被修攏V动词Ş式则H出Refactor是一UY仉构行为,q种重构的方法就是应用一pd的refactoring?/p>
软gl构可以因ؓ各种各样的原因而被改变Q如q行打印化、性能优化{等Q但只有Z可理解性、可修改、可l护目的的改变才是Refactoring。这U改变必M持可观察的行为,按照Martin的话来说Q就是Refactoring之前软g实现什么功能,之后照样实现什么功能。Q何用P不管是终端用戯是其他的E序员,都不需要知道某些东西发生了变化?/p>
Refactoring原则
Two HatsQ两帽子)
Kent Beck提出q个比方。他_如果你在使用Refactoring开发YӞ你把开发时间分l两个不同的zdQ增加功能和refactoring。增加功能时Q你不应该改变Q何已l存在的代码Q你只是在增加新功能。这个时候,你增加新的测试,然后让这些新试能够通过。当你换一帽子refactoringӞ你要C你不应该增加M新功能,你只是在重构代码。你不会增加新的试Q除非发C前漏掉了一个)。只有当你的Refactoring改变了一个原先代码的接口时才改变某些试?/p>
在一个Y件的开发过E中Q你可能频繁C换这两顶帽子。你开始增加一个新功能Q这时你认识刎ͼ如果原来的代码结构更好一点,新功能就能够更方便地加入。因此,你脱下增加功能的帽子Q换上refactoring的帽子。一会儿Q代码结构变好了Q你׃refactoring的帽子,戴上增加功能的帽子。增加了新功能以后,你可能发C的代码得程序的l构难以理解Q这时你又交换帽子?/p>
关于两顶帽子交换的故事不断地发生在你的日常开发中Q但是不你带着哪一定帽子,一定要C带一定帽子只做一件事情?/p>
Unit Test
保持代码的可观察行ؓ不变UCؓRefactoring的安全性。Refactoring工具用半形式化的理论证明来保证Refactoring的安全性?/p>
但是Q要从理Z完全证明pȝ的可观察行ؓ保持不变Q虽然不是说不可能,也是十分困难的。工具也有自q~陷。首先,目前对于Refactoring的理论研Iƈ非十分成熟,某些曄被证明安全的Refactoring最q被发现在特定的场合下ƈ不安全。其ơ,目前的工具不能很好地支持"非正?的Refactoring操作Q如果你发现一U新的Refactoring技巧,工具不能立即让这UrefactoringZ所用?/p>
自动化的试是检验Refactoring安全性非常方便而且有效的方法。虽然我们不能穷整个系l中所有的试Q但如果在Refactoring之前成功的测试现在失败了Q我们就会知道刚刚做的Refactoring破坏了系l的可观察行为。自动化试能够在程序员不进行h工干预的情况下自动检到q样的行为破坏?/p>
自动化测试中最实用的工hXUnitpd单元试框架Q该框架最初由Kent Beck和Eric Gamma为SmalltalkC֛而开?/p>
。Eric GammaҎ试的重要性曾l有q这L话:你写的测试越,你的生力就低Q同时你的代码就变得不E_。你是没有生力、越~少准确性,你承受的压力p?.....
下面的片断来自JavaworldQ两个Sun开发者展CZ它们对单元测试的狂热以及展示了它们扩展单元测试来查象EJBq样的分布式控gQ?br /> 我们从来没有q度试软gQ相反我们很做得够。。。但愿测试是软g开发过E中关键但却l常被误解的一部分。对每一个代码单元而言Q单元测试确保他自己能够工作Q独立于其他单元。在面向对象语言中,一个单元通常Q但q不LQ一个类的等L。如果一个开发者确信应用程序的每一个片断能够按照它们被设计的方式正工作,那么他们会认识到l装得到的应用程序发生的问题必定来自于把所有部件组合v来的q程中。单元测试告诉程序员一个应用程? pieces are working as designed'?/p>
我曾l认己是很好的程序员。认q代码几乎不可能出错。但事实上,我没有Q何证据可以证明这一点,同样我也没有信心我的代码׃定不会出?或者当我增加一Ҏ功能Ӟ原先的行Z定没有遭到破坏。另一斚wQ我认ؓ太多的测试于事无补,试只能停留在理Z上,或只有那些实力强劲的大公司才能做到?/p>
q个观点?999q我看到Kent Beck和Gamma的Junit试框架之后被完全推M。JUnit是XP的重要工具之一。XP提倡一个规则叫做test-first design。采用Test First DesignҎQ你在编写一个新功能前先写一个单元测试,用它来测试实现新功能需要但可能会出错的代码。这意味着Q测试首先是p|的,写代码的目的是Z让这些测试能够成功运行?/p>
JUnit的简单、易用和强大的功能几乎让我立LU了单元试的思想Q不但因为它可以让我有证据表明我的代码是正确的,更重要的是在我每ơ对代码q行修改的同Ӟ我有信心所有的变化都不会媄响原有的功能。测试已l成为我所有代码的一部分。关于这一点,Kent Beck在它的《Extreme Programming Explained》中指出Q?br /> 直不存在一个不带自动化试的程序。程序员~写单元试Q因而他们能够确信程序操作的正确性成为程序本w的一部分。同Ӟ客户~写功能试Q因而他们能够确信程序操作的正确性成为程序本w的一部分。结果就是,随着旉的推U,一个程序变得越来越可信-他变得更加能够接受改变,而不是相反?/p>
单元试的基本过E如下:
在编写测试的时候,要注意对试的内容加以考虑,q不是测试越多越?Kent Beck_
你不必ؓ每一个方法编写一个测试,只有那些可能出错的具有生产力的方法才需要。有时你仅仅x出某些事情是可能的。你探烦一半个时。是的,它有可能发生。现在你抛弃你的代码q且从单元测试重新开始?/p>
另一位作者Eric Gamma_
你L能够写更多的试。但是,你很快就会发玎ͼ你能够想象到的测试中只有一部分才是真正有用的。你所需要的是ؓ那些即你认为它们应当工作还会出错的地方~写试Q或者是你认为可能会p|但最l还是成功的地方。另一U方法是以成?收益的角度来考虑。你应该~写反馈信息物有所值的试?/p>
你可能会认ؓ单元试虽然好,但是它会增加你的~程负担Q而别钱是请你来写代码Q而不是来写测试的。但是WILLAM WAKE_
~写单元试可能是一件乏味的事情Q但是它们ؓ你节省将来的旉Q通过捕获改变后的bugQ?相对不明显,但同样重要的是,他们能够节约你现在的旉Q测试聚焦于设计和实现的单性,它们支持refactoring,它们在你开发一特性的同时对它q行验证?/p>
你还会认为单元测试可能增加你的维护量Q因为如果代码发生了改变Q相应的试也需要做出改变。事实上Q测试只会让你的l护更快Q因为它们让你对你所做出的改变更有信心,如果你做错了一件事Q测试同时也会提醒你。如果接口发生了改变Q你当然需要改变你的接口,但这一点ƈ非太难?/p>
单元试是程序的一部分,而不是独立的试部门所应完成的d。这是所谓的自测试代码。程序员可能p一些时间在~写代码Q花费一些时间在理解别h的代码,p一些时间在做设计,但他们最多的旉是在做调试。Q何一个h都有q样一U遭遇,一个小的问题可能p你一个下午、一天,甚至是几天的旉来调试。要Ҏ一个bug往往很简单,但是要找到这Lbug却是一个大问题。如果你的代码能够带有自动化的自试Q那么一旦你加入一个新的功能,旧的试会告诉你那些原来的代码存在着bugQ而新加入的测试则告诉哪些新加入的代码引入了bug?/p>
Small step
Refactoring的另一个原则就是每一步L做很的工作Q每做少量修改,p行测试,保证refactoring的程序是安全的?/p>
如果你一ơ做了太多的修改Q那么就有可能介入很多的bugQ代码将难以调试。如果你发现修改q不正确Q要惌回到原来的状态也十分困难?/p>
q些l小的步骤包括:
要求使用步骤渐q地Refactoringq不完全Z对实跉|行的考虑?/p>
Ralph Johnson在伊利诺斯州立大学领导的一个研I小l是Refactoring理论的引D和最重要的理论研I团体。其中William Opdyke 1992q的博士论文《Refactoring Object-Oriented Framework》是公认的RefactoringW一位正式提?/p>
Z么要 Refactoring
Z么要L变已l可以正运行的软gQ这L改变是否影响到我们的设计Q从而进一步改变我们对于面向对象系l进行设计的Ҏ和思\Q本部分试图回答q些问题?/p>
Refactoring虽然需要更多的"额外工作"Q但是它l我们带来的各种好处昄值得我们做出q样的努力:
化测?/strong>
一个好的Refactoring实现能够减少Ҏ设计的测试量.因ؓRefactoring的每一步都保持可观察的行ؓ,也就是保持系l的所有单元测试都能顺利通过。所以只有发生改变的代码需要测?q种增量试使得所有的后箋试都徏立在坚实的基之上Q整个系l测试的复杂性大大降低?/p>
更简单的设计
Refactoring降低初始设计的复杂程?Gamma指出复杂设计模式的一个陷阱是q度狂热:"模式有其成本(间接性、复杂化)Q因此设计应该达到需求所要求的灵zL,而不是越灉|好"。如果设计试图介入太多以后可能需要的灉|性,׃产生不必要的复杂和错误。Refactoring能够以多U方式扩展设计。他鼓励为手头的d建立刚刚合适的解决ҎQ当新的需求来到时Q可以通过Refactoring扩展设计?/p>
Refactoring增进软g可理解?/strong>
E序的最l目的是Z指引计算机完成h们需要完成的事情。但是,要完成这个目标ƈ非想象的那么Ҏ?/p>
E序~写是h的活动,人首先要理解才能行动。所以,源代码的另一个作用就是用于交的工具。其他h可能会在几个月之后修改你的代码,如果q理解你的代码都做不刎ͼ他又如何完成所需的修改呢Q我们通常会忘掉源代码的这U用处,管它可能是源代码更重要的用处。不Ӟ我们Z么发展高U语a、面向对象语aQؓ什么我们不直接使用汇编语言甚至是机器语a来编写程序?N我们真的在意计算机多׃几个CPU周期d成一件事Q?/p>
如果一个h能够理解我们的代码,他可能只需要一天的旉完成一个增加功能的dQ而如果他不理解我们的代码Q可能需要花上一个礼拜或更长的时间。这里的问题是,我们在编写代码的时候不但需要考虑计算机CPU的想法,更要把以后的开发者放在心上。除非,你写代码的唯一目的是把它丢掉。你不想让Q何别的开发h员用到这D代码,包括你自己。因Z不可能记得所有你写过的代码,如果你经常回q头ȝ一下自q代码Q你׃体会C码的可理解是如何重要?/p>
Refactoring可以使得你的代码更理解,Refactoring支持更小的类、更短的Ҏ、更的局部变量、更的pȝ耦合QRefactoring要求你更加小心自q命名机制Q让名字反映Z的意图。如果哪一块代码太复杂以至于哪一理解Q你都需要对他进行Refactor?/p>
你可能认为,反映代码的意囑ֺ当是注释和文档的责Q。假设你写好了一D늨序,l他加上注释Q来说明你这D代码完成了什么。每ơ代码发生改变,你都需要修Ҏ释。而其实代码本w应当以说明这个问题。如果代码不能反映自q意图Q即使是再多的注释也不以让你理解代码的所有机Ӟ除非你把代码的每一句话都加上注释。甚之,q种重复的责M得一旦注释和代码发生不一_它反而会ȝ你对代码的理解。而Q何一个程序员都不愿意写太多的注释和文档。所以Martin fowler_
When you feel the need to write a commentQfirst try to refactor the code so that any comment becomes superfluous?/p>
Martin Fowler同时指出QRefactoring不但能够增加别h对你代码的理解,而且是一U非常好的理解别Z码,学习别h代码的方法。通常Q当你拿C大堆代码Ӟ你可能会觉得一片茫Ӟ不知道从何处开始。学习别Z码最好的Ҏ是对代码进行进行Refactor。如果你发现自己不能理解一D代码,那么试图使用对自pQ哦Q这D代码可能是在做什么事情。给他一个有意义的名字,refactor它。Refactor使得你对代码的理解不是仅仅停留在脑袋中,而是看到代码实按照你的理解再发生变化。如果你的refactor改变了系l的行ؓQ那么说明你的理解还有问题。你必须q回重来?/p>
Refactoring 改进软g的设?/strong>
许多人把~程看作是一U低U活动。他们认Z码仅仅是设计的附属物。然而正是代码,而不是你脑袋里或U怸的设计,真正驱动计算机完成你惛_的事情。而绝大多数的bug也生于~码阶段?/p>
很多Ҏ学认为通过分析和设计的严格化就能生更高质量的软gQ这实际上是不可能的。正如Brain Foote和Joseph Yoder在《Big Ball of MUD》一文中指出Q虽然许多作者提Z很多理想上非常完的体系l构Q如Layer、PIPELINE{等。但在实践中Q几乎从来都不能看到q么l构清晰的系l?/p>
q一斚w是由于分析和设计一个领域的应用E序需要对该领域丰富的知识Q而这U知识不是在一开始都能获得的。我们通常需要在实践反馈的过E中才能一步步加深自己对该领域的理解。因而,我们一开始的设计可能q不能正反映系l的内在本质Q所以也不可能在代码中得到很好的反映?/p>
另一斚wQ即使一开始的设计是完好的Q随着用户对系l用的深入Q新的需求可能会被加入,旧的需求会被修攏V删除。一个最先的设计不可能完全预料到q些变化?/p>
一旦实现开始偏L初的设计Q那么它的代码将不受控制Q从而不可避免地开始腐蚀。代码加入越多,腐蚀的速度快。如果没有办法让设计可能地与实C持一_那么q种腐蚀的最后结果就是代码不得不被抛弃?/p>
但是让代码实现和设计始终保持一致ƈ非那么简单,在传l的软gҎ中,一旦开发到了实现阶D,很隑֯设计做出变化。所以最q发展的面向对象Ҏ学把增量q代QiterativeQ作Z个基本原则?/p>
也许是题外话Q在目前最为时行的一?重型"软gҎ学中Q我们很难找到对q代Q包括设计的q代Q明的支持、定义和操作q程Q以及如何提高程序员适应q种动态开发能力的Ҏ。在我看来,重型的Y件方法学即能够对Y件开发vC定的作用Q他也不可能包罗万象Q解册Y件开发中的所有问题。开发方法学重视生命周期理和控Ӟ但Y件的开发ƈ不只有过E和生命周期Q更重要的是Q同时往往?正规"的Y件方法所忽略的是QY件开发是人的zd。不你的过E控制是多么的严|不管你的生命周期模型是多么的完美Q如果不能提高h的生产力Q提高品的质量Q那么一切都是毫无意义的?/p>
在这里,要实现这样一U增量P代的开发模型,你必能够让分析人员、设计h员、程序h员、测试h员等{,所有参于开发活动的人有能力或者能够有切实可行的方法来实现q代Q实现增量。如果不是这L话,你无法随时保持实现和设计之间的一致性,无法把编码实C所发现的不合理设计反馈到初始设计,也无法在需求变化时对实现所产生的媄响准及时地得到反映?/p>
如果没有切实可行的基本方法来支持q代中所需要的改变,那么q代是非常困难?我们可以设想,一旦新的需求到?新的一轮P代开?而原先的pȝ设计被证明无法适应现在的变?q个时候你如何能够使得q代利C增量的方式进行?/p>
public class Singleton { private static Singleton instance; // Private constructor suppresses generation of a (public) default constructor private Singleton() {} public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
public class Singleton { // Private constructor suppresses generation of a (public) default constructor private Singleton() {} private static class SingletonHolder { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
public class Singleton { private volatile static Singleton instance; // Private constructor suppresses generation of a (public) default constructor private Singleton() {} public static Singleton getInstance() { if(instance == null) { synchronized(Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }