重构-改善既有代码的设计--罗书赟2011年3月10日目录重构概述代码的坏味道重构名录构筑测试体系Q&A重构是对软件内部结构的一种调整,目的是在不改变外部行为的前提下,提高可理解性,降低修改成本。重构是严谨、有序地对完成的代码进行整理从而减少出错的一种方法。什么是重构?重构概述重构概述利用重构技术开发软件时会把时间分配给两种行为:[重构]与[添加新功能]添加新功能时,不应该修改既有代码,只管添加新功能。重构时你就不能再添加功能,只管改进程序结构。两顶“帽子”可交替进行,一会重构,一会添加新功能。两顶帽子重构概述改进程序设计程序员为了快速完成任务,在没有完全理解整体架构之前就修改代码,导致程序逐渐失去自己的结构。重构则帮助重新组织代码,重新清晰的体现程序结构和进一步改进设计。提高程序可读性容易理解的代码很容易维护和增加新功能。代码首先是写给人看的,然后才是计算机看的。为何重构?重构概述助你找到程序错误重构是一个CodeReview和反馈的过程。在另一个时段重新审视代码,会容易发现问题和加深对代码的理解。助你提高编程速度设计和代码的改进都可以提高开发效率,好的设计和代码都提高开发效率的根本。提高设计和编码水平对代码的重构,是快速提高设计和编码水平的方法。为何重构?重构概述增加新功能时一并重构增加功能前需要理解修改的代码,如果发现代码不易理解且无法轻松增加功能,此时就需要对代码进行重构。修补错误时一并重构通过重构改善代码结构,能够帮助你找出BUG原因。Review代码时一并重构有经验的开发人员Review代码时能够提出一些代码重构的建议。何时重构?重构概述代码实在太混乱,重构还不如重写项目即将结束时避免重构此时已经没有时间进行重构了,应该在早些时候进行重构。如果程序有必要重构,说明该项目已经欠下“债务”,需要项目完成后进行偿还。何时不该重构?重构概述重构与设计彼此互补良好的设计是重构的目标,重构弥补设计的不足。重构使得设计方案更简单如果选择重构,预先设计时候只需找出足够合理的解决方案,实现的时候对问题会进一步加深,此时可以重构成最佳的解决方案。重构能够避免过度设计设计人员需要考虑将简单方案重构成灵活方案的难度。如果容易,只需实现简单方案。重构与设计1.重复的代码(DuplicatedCode)☆☆☆☆☆重复代码是最常见的异味,往往是由于Copy&Paste造成的。重构方法:重复代码在同一个类中的不同方法中,则直接提炼为一个方法如果重复代码在两个互为兄弟的子类中,则将重复的代码提到父类中如果代码类似,则将相同部分构成单独函数,或者用TemplateMethod设计模式代码的坏味道☆表示坏味道指数代码的坏味道重复代码出现在不相干的类中,则将代码提炼成函数或者放在独立的类中代码的坏味道2.过长的函数(LongMethod)☆☆☆☆☆是面向结构程序开发带来的“后遗症”,过长的函数降低可读性。重构方法:将独立的功能提炼成新函数3.过大类(LargeClass)☆☆☆☆过大的类使得责任不清晰。重构方法将过大类的功能拆分成多个功能单一的小类代码的坏味道4.过长的参数列(LongParameterList)☆☆☆☆过长的参数列难以理解,而且容易传错参数。重构方法:将参数列表用参数对象替换代码的坏味道5.发散式变化(DivergentChange)☆☆☆一个类由于不同的原因而被修改。重构方法:将类拆分成多个,每个类只因为一种变化而修改发散式变化实例代码的坏味道包含多种证券的业务逻辑将业务逻辑放到证券类中代码的坏味道6.霰弹式修改(ShotgunSurgery)☆☆☆☆与发散式变化相反,遇到变化时需要修改许多不同的类。重构方法:将类似的功能放到一个类中霰弹式修改实例代码的坏味道计算逻辑分散在各个类中计算逻辑放到股指期货类中代码的坏味道7.依恋情结(FeatureEnvy)☆☆☆函数对某个类的兴趣高过对自己所处的类,通常是为了取其他类中的数据。重构方法:将函数部分功能移到它感兴趣的类中8.数据泥团(DataClumps)☆☆☆在多个地方看到相同的数据项。例如:多个类中相同的变量,多个函数中相同的参数列表,并且这些数据总是一起出现。重构方法:将这些数据项放到独立的类中代码的坏味道9.分支语句(SwtichStatements)☆☆☆☆大量的分支、条件语句导致过长的函数,并且可读性差。重构方法:应将它变成子类或者使用State和Strategy模式分支语句实例代码的坏味道抽象接口,只与接口交互if语句太多,结构不清晰代码的坏味道10.过度耦合的消息链(MessageChains)☆☆☆一个对象请求另一个对象,后者又请求另外的对象,然后继续。。。。,形成耦合的消息链。重构方法:公布委托对象供调用代码的坏味道11.过多的注释(Comments)☆☆代码有着长长的注释,但注释之所以多是因为代码很糟糕。重构方法:先重构代码,再写上必要的注释12.夸夸其谈未来性(SpeculativeGenerality)☆☆☆现在用不到,觉得未来可以用到的代码,要警惕。重构方法:将用不上的代码去掉代码的坏味道13.纯粹的数据类(DataClass)☆☆☆将数据类中数据以Public方式公布,没对数据访问进行保护。重构方法:将数据封装起来,提供Get/Set方法以上是代码开发和程序维护过程中经常遇到的问题,并不是坏味道的全部。在开发中应避免出现坏味道。重构名录在MartinFowler著的《重构–改善既有代码的设计》中列出了长达70条的重构名录,提供了具体重构的方法和重构的技巧。将帮助开发人员一次一小步地修改代码,减少了开发过程中的风险。1、提炼函数(ExtractMethods)重构名录Stringname=request.getParameter(Name);if(name!=null&&name.length()0){......}Stringage=request.getParameter(Age);if(age!=null&&age.length()0){......}Stringname=request.getParameter(Name);if(!isNullOrEmpty(name)){......}Stringage=request.getParameter(Age);if(!isNullOrEmpty(age)){......}privatebooleanisNullOrEmpty(finalStringstring){if(string!=null&&string.length()0){returntrue;}else{returnfalse;}}重构名录实例将代码段放入函数中,让函数名称解释该函数的用途2、将函数内联化(InlineMethod)重构名录如果函数的逻辑太简单,则把其移到调用它的代码中,取消这个函数3、将临时变量内联化(InlineTemp)重构名录变量被一个简单的表达式赋值一次,则将变量替换成那个表达式4、以查询取代临时变量(ReplaceTempwithQuery)重构名录doublebasePrice=_quantity*_itemPrice;if(basePrice1000)returnbasePrice*0.95;elsereturnbasePrice*0.98;if(basePrice()1000)returnbasePrice()*0.95;elsereturnbasePrice()*0.98;...doublebasePrice(){return_quantity*_itemPrice;}临时变量保存表达式的结果,将这个表达式提炼到独立的函数中。5、引入解释性变量(IntroduceExplainingVariable)重构名录将复杂表达式结果放入临时变量,用变量名来解释表达式用途booleanisMacOs=platform.toUpperCase().indexOf(MAC)-1;booleanisIEBrowser=browser.toUpperCase().indexOf(IE)-1;booleanwasResized=resize0;if(isMacOs&&isIEBrowser&&wasInitialized()&&wasResized){//dosomething}if((platform.toUpperCase().indexOf(MAC)-1)&&(browser.toUpperCase().indexOf(IE)-1)&&wasInitialized()&&resize0){//dosomething}6、剖解临时变量(SplitTemporaryVariable)重构名录一个临时变量多次被赋值(不在循环中),应该针对每次赋值,创造独立的临时变量。doubletemp=2*(_height+_width);System.out.println(temp);temp=_height*_width;System.out.println(temp);doubleperimeter=2*(_height+_width);System.out.println(perimeter);doublearea=_height*_width;System.out.println(area);7、以卫语句取代嵌套条件语句(ReplaceNestedConditionalwithGuardClauses)重构名录函数中条件语句使人难以看清正常的执行路径,用卫语句替换嵌套条件doublegetPayAmount(){doubleresult;if(_isDead)result=deadAmount();else{if(_isSeparated)result=separatedAmount();else{if(_isRetired)result=retiredAmount();elseresult=normalPayAmount();};}returnresult;};doublegetPayAmount(){if(_isDead)returndeadAmount();if(_isSeparated)returnseparatedAmount();if(_isRetired)returnretiredAmount();returnnormalPayAmount();};8、分解条件表达式(DecomposeConditional)重构名录从复杂的条件语句分支中分别提炼出独立函数if(date.before(SUMMER_START)||date.after(SUMMER_END))charge=quantity*_winterRate+_winterServiceCharge;elsecharge=quantity*_summerRateif(notSummer(date))charge=winterCharge(quantity);elsecharge=summerCharge(quantity);构筑测试体系如果你想进行重构,首先要拥有一个可靠的自动化测试环境。自动化测试代码的价值程序员代码编写只占小部分时间,大部分时间用于调试和查找BUG。自动化测试能够大幅减少由于重构代码及新增功能引人的BUG。XUnit测试框架XUnit是一个单元测试框架,用于编写自动化测试用例。每次对代码重构后运行一遍测试用例,检查是否引入了Bug。引用译者的一段话作为结束:‘重构已经变成了我的另外一种生活方式,变成了我每天的面包与黄油,变成了我们整个团队的空气与水,以至于无须到书中寻找任何神谕。’谢谢大家!Q&A