重构,-改善既有代码的设计,-,罗书赟,2011,年,3,月,10,日,目录,重构,是对软件内部结构的一种调整,目的是在不改变外部行为的前提下,提高可理解性,降低修改成本。,重构是严谨、有序地对完成的代码进行整理从而减少出错的一种方法。,什么是重构?,重构概述,重构概述,利用重构技术开发软件时会把时间分配给两种行为:,重,构,与添加新功能,添加新功能时,不应该修改既有代码,只管添加,新功能。,重构时你就不能再添加功能,只管改进程序结构。,两顶“帽子”可交替进行,一会重构,一会添加,新功能。,两顶帽子,重构概述,改进程序设计,程序员为了快速完成任务,在没有完全理解整体 架构之前就修改代码,导致程序逐渐失去自己的结构。,重构则帮助重新组织代码,重新清晰的体现程序结构和进一步改进设计。,提高程序可读性,容易理解的代码很容易维护和增加新功能。代码首先是写给人看的,然后才是计算机看的。,为何重构?,重构概述,助你找到程序错误,重构是一个,Code Review,和反馈的过程。在另 一个时段重新审视代码,会容易发现问题和加深对代码的理解。,助你提高编程速度,设计和代码的改进都可以提高开发效率,好的设计和,代码都,提高开发效率的根本。,提高设计和编码水平,对代码的重构,是快速提高设计和编码水平的方法。,为何重构?,重构概述,增加新功能时一并重构,增加功能前需要理解修改的代码,如果发现代码不易理解且无法轻松增加功能,此时就需要对代码进行重构。,修补错误时一并重构,通过重构改善代码结构,能够帮助你找出,BUG,原因。,Review,代码时一并重构,有经验的开发人员,Review,代码时能够提出一些代码重构的建议。,何时重构?,重构概述,代码实在太混乱,重构还不如重写,项目即将结束时避免重构,此时已经没有时间进行重构了,应该在早些时候进行重构。如果程序有必要重构,说明该项目已经欠下“债务”,需要项目完成后进行偿还。,何时不该重构?,重构概述,重构与设计彼此互补,良好的设计是重构的目标,重构弥补设计的不足。,重构使得设计方案更简单,如果选择重构,预先设计时候只需找出足够合理的解决方案,实现的时候对问题会进一步加深,此时可以重构成最佳的解决方案。,重构能够避免过度设计,设计人员需要考虑将简单方案重构成灵活方案的难度。如果容易,只需实现简单方案。,重构与设计,重复的代码,(,Duplicated Code,),重复代码是最常见的异味,往往是由于,Copy&Paste,造成的。,重构方法,:,重复代码在同一个类中的不同方法中,则直接提炼为一个方法,如果重复代码在两个互为兄弟的子类中,则将重复的代码提到父类中,如果代码类似,则将相同部分构成单独函数,或者用,Template Method,设计模式,代码的坏味道,表示坏味道指数,代码的坏味道,重复代码出现在不相干的类中,则将代码提炼成函数或者放在独立的类中,代码的坏味道,过长的函数,(Long Method),是面向结构程序开发带来的,“,后遗症,”,,过长的函数降低可读性。,重构方法,:,将独立的功能提炼成新函数,3.,过大类,(Large Class),过大的类使得责任不清晰。,重构方法,将过大类的功能拆分成多个功能单一的小类,代码的坏味道,4.,过长的参数列,(Long Parameter List),过,长的参数列难以理解,而且容易传错参数。,重构方法,:,将参数列表用参数对象替换,代码的坏味道,5.,发散式变化,(Divergent Change),一个类由于不同的原因而被修改。,重构方法,:,将类拆分成多个,每个类只因为一种变化而修改,发散式变化实例,代码的坏味道,包含多种证券,的业务逻辑,将业务逻辑放到,证券类中,代码的坏味道,6.,霰弹式修改,(Shotgun Surgery),与发散式变化相反,遇到变化时需要修改许多不同的类。,重构方法,:,将类似的功能放到一个类中,霰弹式修改实例,代码的坏味道,计算逻辑分散,在各个类中,计算逻辑放到,股指期货类中,代码的坏味道,7.,依恋情结,(Feature Envy),函数对某个类的兴趣高过对自己所处的类,通常是为了取其他类中的数据。,重构方法,:,将函数部分功能移到它感兴趣的类中,8.,数据泥团,(Data Clumps),在多个地方看到相同的数据项。例如:,多个类中相同的变量,多个函数中相同的参数列表,并且这些数据总是一起出现。,重构方法,:,将这些数据项放到独立的类中,代码的坏味道,9.,分支语句,(Swtich Statements),大量的分支、条件语句导致过长的函数,并且可读性差。,重构方法,:,应将它变成子类或者使用,State,和,Strategy,模式,分支语句实例,代码的坏味道,抽象接口,只与接口交互,if,语句太多,结构不清晰,代码的坏味道,10.,过度耦合的消息链,(Message Chains),一个对象请求另一个对象,后者又请求另外的对象,然后继续。,形成耦合的消息链。,重构方法:,公布委托对象供调用,代码的坏味道,11.,过多的注释,(Comments),代码有着长长的注释,但注释之所以多是因为代码很糟糕。,重构方法:,先重构代码,再写上必要的注释,12.,夸夸其谈未来性,(Speculative Generality),现在用不到,觉得未来可以用到的代码,要警惕。,重构方法:,将用不上的代码去掉,代码的坏味道,13.,纯粹的数据类,(Data Class),将数据类中数据以,Public,方式公布,没对数据访问进行保护。,重构方法:,将数据封装起来,提供,Get/Set,方法,以上是代码开发和程序维护过程中经常遇到的问题,并不是坏味道的全部。在开发中应避免出现坏味道。,重构名录,在,Martin Fowler,著的,重构,改善既有代码的设计,中列出了长达,70,条的重构名录,提供了具体重构的方法和重构的技巧。将帮助开发人员一次一小步地修改代码,减少了开发过程中的风险。,1,、,提炼函数,(,Extract Methods,),重构名录,String name=request.getParameter(Name);,if,(name!=null&name.length()0),.,String age=request.getParameter(Age);,if,(age!=null&age.length()0),.,String name=request.getParameter(Name);,if,(!,isNullOrEmpty,(name),.,String age=request.getParameter(Age);,if,(!,isNullOrEmpty,(age),.,private boolean,isNullOrEmpty,(final String string),if(string!=null&string.length()0),return true;,else,return false;,重构名录实例,将代码段放入函数中,让函数名称解释该函数的用途,2,、,将函数内联化,(Inline Method),重构名录,如果函数的逻辑太简单,则把其移到调用它的代码中,取消这个函数,3,、,将临时变量内联化(,Inline Temp,),重构名录,变量被一个简单的表达式赋值一次,则将变量替换成那个表达式,4,、,以查询取代临时变量(,Replace Temp with Query,),重构名录,double,basePrice=_quantity*_itemPrice;,if,(basePrice 1000),return,basePrice*0.95;,else,return,basePrice*0.98;,if,(basePrice()1000),return,basePrice()*0.95;,else,return,basePrice()*0.98;,.,double,basePrice(),return,_quantity*_itemPrice;,临时变量保存表达式的结果,将这个表达式提炼到独立的函数中。,5,、,引入解释性变量(,Introduce Explaining Variable,),重构名录,将复杂表达式结果放入临时变量,用变量名来解释表达式用途,boolean,isMacOs =platform.toUpperCase().indexOf(MAC)-1;,boolean,isIEBrowser=browser.toUpperCase().indexOf(IE)-1;,boolean,wasResized =resize 0;,if(isMacOs&isIEBrowser&wasInitialized()&wasResized),/do something,if,(platform.toUpperCase().indexOf(MAC)-1)&,(browser.toUpperCase().indexOf(IE)-1)&,wasInitialized()&resize 0),/do something,6,、,剖解临时变量(,Split Temporary Variable,),重构名录,一个临时变量多次被赋值(不在循环中),应该针对每次赋值,创造独立的临时变量。,double,temp,=2*(_height+_width);,System.out.println(,temp,);,temp,=_height*_width;,System.out.println(,temp,);,double,perimeter,=2*(_height+_width);,System.out.println(,perimeter,);,double,area,=_height*_width;,System.out.println(,area,);,7,、,以卫语句取代嵌套条件语句,(,Replace Nested Conditional with Guard Clauses,),重构名录,函数中条件语句使人难以看清正常的执行路径,用卫语句替换嵌套条件,double,getPayAmount,(),double,result;,if,(_isDead)result=deadAmount();,else,if,(_isSeparated)result=separatedAmount();,else,if(_isRetired)result=retiredAmount();,else result=normalPayAmount();,;,return,result;,;,double,getPayAmount,(),if,(_isDead),return,deadAmount();,if,(_isSeparated),return,separatedAmount();,if,(_isRetired),return,retiredAmount();,return,normalPayAmount();,;,8,、,分解条件表达式,(,Decompose Conditional,),重构名录,从复杂的条件语句分支中分别提炼出独立函数,if,(date.before(SUMMER_START)|date.after(SUMMER_END),charge=quantity*_winterRate+_winterServiceCharge;,else,charge=quantity*_summerRate,if,(notSummer(date),charge=winterCharge(quantity);,else,charge=summerCharge(quantity);,构筑测试体系,如果你想进行重构,首先要拥有一个可靠的自动化测试环境。,自动化测试代码的价值,程序员代码编写只占小部分时间,大部分时间用于调试和查找,BUG,。自动化测试能够大幅减少由于重构代码及新增功能引人的,BUG,。,XU