代码重构2018年1月12日主要内容重构的定义为什么需要重构何时需要重构代码的坏味道重构的手法名言任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员–MartinFowler重构的定义Refactoring(名词):对软件内部结构的一种调整,目的是在不改变软件外部行为下,提高其可理解性,降低其修改成本。Refactor(动词):使用一系列重构准则(手法),在不改变外部行为的前提下,调整其结构。为什么需要重构改进软件的设计。程序员对代码所做的为了满足短期利益代码改动,或再没有完全清楚增个架构下的改动,都很容易是代码失去它的清晰结构,偏离需求或设计。而这些改动的积累很容易使代码偏离它原先设计的初衷而变得不可立即和无法维护。Refactoring则帮助重新组织代码,重新清晰的体现结构和进一步改进设计。为什么需要重构提高代码质量,更易被理解容易理解的代码可以很容易的维护和做进一步的开发。即使对写这些代码的程序员本身,容易理解代码也可以帮助容易地做修改。程序代码也是文档。而代码首先是写给人看的,让后才是给计算机看的。为什么需要重构Refactoring帮助尽早的发现错(Bugs)Refactoring是一个codereview和反馈的过程。在另一个时段重新审视自己或别人代码,可以更容易的发现问题和加深对代码的理解。Refactoring是一个良好的软件开发习惯。为什么需要重构Refactoring可以提高开发速度Refactoring对设计和代码的改进,都可以有效的提高开发速度。好的设计和代码质量实体提高开发速度的关键。在一个有缺陷的设计和混乱代码基础上的开发,即使表面上进度较快,但本质是试延后对设计缺陷的发现和对错误的修改,也就是延后了开发风险,最终要在开发的后期付出更多的时间和代价。项目的维护成本远高于开发成本.何时重构?三次法则第三次做类似的事情是,就需要重构添加新功能时一并重构重构是添加新功能最快捷的途径,一旦完成重构,新特性的添加就会更快速、更流畅。修补错误时一并重构重构能加深对代码的理解,是代码更加清晰,能帮助我们找出错误bug,重构能帮助写出更健壮的代码。何时不该重构?代码太混乱,设计完全错误。与其重构,不如重写。如果项目已近最后期限,应该避免重构.如果最后没有足够的时间重构,通常表示早该进行重构。两顶帽子[重构]与[添加新功能]添加新功能时,你不应该修改既有代码,只管添加新功能。重构时你就不能再添加功能,只管改进程序结构。此外你不应该添加任何测试(除非发现有先前遗漏的东西)两顶“帽子”可同时进行,一会重构,一会添加新功能。重构与设计重构可以带来更简单的设计,同时又不失灵活性,降低了设计过程的难度,减轻了设计的压力。通常情况下我们的设计不是能贯穿我们软件开发的全过程的,在这个过程中,我们的需求变更的可能性非常大,当需求变了,设计也得变,但是我们已有的实现怎么办?全部废除?显然不能!这时候就要依靠重构来解决这种矛盾。重构与性能关于重构,有一个常被提出的问题:它对程序的性能将造成怎样的影响?为了让软件易于理解,你常会作出一些使程序运行变慢的修改。这是个重要的问题。但是,换个角度说,虽然重构必然会使软件运行更慢,但它也使软件的性能优化更易进行。重构与模式那么真正要实现重构时,我们有哪些具体的方法呢?可以这样说,重构的准则由很多条。但它不是最终的标准,因为你要是完全按照它的标准来执行,那你也就等于不会重构,重构是一种武器,而真正运用武器的高手是没有武器胜有武器。只有根据实际的需要,凭借一定的思想,才能实现符合实际的重构,我们不能被一些固定的模式套牢了,这样你的程序会很僵化。究竟如何把握这个度,需要大家去总结。重构与思想要想实现一个好的重构,不是重构本身,而是我们在写代码的时候,思想当中时刻有它的位置存在!非常重要!如果你本身就没想着要去重构,那么就是有再好的模式供你调用又怎么样?就是有了好的模式,你不能根据实际的需要去融会贯通,那你做出来的重构有意义么?重构的起源WardCunningham和KentBeck,Smalltalk里有影响力的专家KentBeck,极限编程的领导者RalphJohnson,伊利诺伊大学教授,“GangofFour”之一BillOpdyke,Ralph的博士生MartinFowler重构:改善现有代码的设计重复的代码(DuplicatedCode)过长的函数(LongMethod)过大的类(LargeClass)过长的参数列(LongParameterList)发散式变化(DivergentChange)霰弹式修改(ShotgunSurgery)依恋情结(FeatureEnvy)数据泥团(DataClumps)基本类型偏执(PrimitiveObsession)Switch语句(SwtichStatements)平行继承体系(ParallelInheritanceHierarchies)冗赘类(LazyClass)代码的坏味道夸夸其谈未来性(SpeculativeGenerality)令人迷惑的暂时值域(TemporaryField)过度遇合的消息链(MessageChains)中间转手人(MiddleMan)狎昵关系(InappropriateIntimacy)异曲同工的类(AlternativeClasseswithDifferentInterfaces)不完善的程序库类(IncompleteLibraryClass)纯粹的数据类(DataClass)被拒绝的遗赠(RefusedBequest)过多的注释(Comments)代码的坏味道如果你在一个以上的地点看到相同的程序结构,那么可以肯定:设法将它们和而为一,程序会变得更好。在同一个类中出现重复代码。这时可以采用ExtractMethod(提炼函数)提炼出重复的代码,然后让这2个地点都调用被提炼出来的那段代码。在两个互为兄弟的子类中出现重复代码。只需对2个类都是用ExtractMethod(提炼函数),然后对被提炼出来的函数是用PullUpMethod(方法上移),将它推入超类。在两个毫不相关的类中出现重复代码。其中一个类运用ExtractClass(提炼类),将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。重复的代码(DuplicatedCode)函数太长,逻辑混乱,临时变量太多等。可读性差,引起其它坏味道导致维护成本高。99%的情况下,要把函数变小,只需要使用ExtractMethod(提炼函数)新函数。函数有大量参数或临时变量。尝试使用ExtractMethod(提炼函数),将会把大量参数或临时变量当作参数传递到新函数中。不可取!运用ReplaceTempwithQuery(以查询取代临时变量)来消除临时变量。运用IntroduceParameterObject(引入参数对象)和PreserveWholeObject(保持对象完整)将过长的参数列表变得简洁。如果仍有太多临时变量和参数,ReplaceMethodwithMethodObject(以函数对象取代函数)。条件和循环也是提炼的信号,可以使用DecomposeConditional(分解条件表达式)处理条件式,至于循环,可以分别将循环和其内的代码分别提炼到新的函数中。过长的函数(LongMethod)单个类做太多的事,其内往往就会出现太多实例变量。选择class内彼此相关的变量,运用ExtractClass(提炼类)将它们一起提炼到新的class内。如果它适合作为一个子类,则使用ExtractSubclass(提炼子类)比较适合。确定客户端如何使用代码,运用ExtractInterface(提炼接口)为每一种使用方式提炼一个接口。将GUI类的数据和行为移至独立的领域对象,对于两边同时保留的重复数据,运用DuplicateObservedData(复制“被监视数据”)同步。过大的类(LargeClass)出现过长的参数列,我们在编写程序的时候职责划分不清晰,一个函数做了太多的事情,可能会让调用者传入更多的参数进行功能的实现。第二函数封装不合理,导致调用函数的内部变量成为封装函数的参数。太长的参数列难以理解,太多参数会造成前后不一致,不易使用,而且一旦你需要更多数据,就不得不修改它.检查什么值被传入参数,如果向已有的对象发出一条请求就可以取代一个参数,那么你应该使用以函数取代参数(ReplaceParameterwithMethods)。在这里,“已有的对象”可能是函数所属类里的一个字段,也可能是另一个参数。你还可以运用保持对象完整(PreserveWholeObject)将来自同一对象的一堆数据收集起来,并以该对象替换它们。如果某些数据缺乏合理的对象归属,可使用引入参数对象(IntroduceParameterObject)为它们制造出一个“参数对象”。过长的参数列(LongParameterList)如果某个类经常因为不同的原因在不同的方向上发生变化,发散式变化(DivergentChange)就出现了。那么此时也许将这个对象分成2个会更好,这么一来每个对象就可以只因1种变化而需要修改。针对某以外界变化的所有相应修改,都只应该发生在单一类中,而这个新类内的所有内容都应该反应此变化。为此应该找出某特定原因而造成的所有变化,然后运用ExtractClass(提炼类)将它们提炼到另一个类中。发散式变化(DivergentChange)霰弹式修改(ShotgunSurgery)散弹式修改(ShotgunSurgery)类似发散式变化(DivergentChange),但恰恰相反。如果每遇到某种变化,都必须在许多不同的类中做出许多小修改,你所面临的坏味道就是散弹式修改(ShotgunSurgery)。如果需要修改的代码散布在四处,你不但很难找到它们,也很容易忘记某个重要的修改。可以使用MoveMethod(搬移函数)和MoveField(搬移字段)把所有需要修改的代码放进同1个类。如果暂时没有合适的类,就创建一个。通常可以运用InlineClass(将类内联化)把一系列相关行为放进同一个类。这可能会造成少量的发散式变化(DivergentChange),但可以轻易出来它。发散式变化(DivergentChange)是指一个类受多种变化的影响,散弹式修改(ShotgunSurgery)则是指一种变化引起多个类相应修改。这2种情况都会希望整理代码,使外界变化与需要修改的类趋于一一对应。依恋情结(FeatureEnvy)函数对某个类的兴趣高过对自己所处的类,通常的焦点就是数据,某个函数为了计算某个值,从另一个对象那儿调用几乎半打的取值函数。这时一个运用MoveMethod(搬移函数)把它移到自己该去的地方。有时候函数中只有一部分受这种依恋之苦,这时候使用ExtractMethod(提炼函数)把这部分提炼到独立函数中,再使用MoveMethod(搬移函数)带它去它的梦中家园。一个函数往往会用到几个类的功能,那么它究竟该被置与何处呢?原则:判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。如果先以ExtractMethod(提炼函数)将这个函数分解为数个较小函数并分别置于不同地点,上述步骤就比较容易完成了。数据泥团(DataClumps)常常可以在很多地方看到相同的3、4项数据:2个类中相同的字段、许多函数签名中相同的参数。这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。首先找出这些数据以字段形式出现的地方,运用ExtractClass(提炼类)将它们提炼到一个独立对象中。然后将注意力转移到函数签名上,运用IntroduceParameterObject(引入参数对象)或PreserveWholeObject(保持对象完整)为它减肥。