`
碧海山城
  • 浏览: 190681 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论
阅读更多
重构

重构:(名)在软件内部结构的一种调整,目的是在不改变[软件之可察行为]前提下,提高其可理解性,降低其修改成本。
重构:(动)使用一系列重构准则(手法),在不改变[软件之可察行为]前提下,调整其结构。

在使用重构技术开发软件时,把自己的时间分配给两种截然不同的行为:[添加新功能]和[重构]。添加新功能时,
    不应该修改既有代码,只管添加新功能,并通过测试。重构时不应该再添加新功能,只管改进程序结构。此时不该添加任
    何测试,只在绝对必要(用以处理接口变化)时才修改测试。

何时重构

1.三次法则,事不过三,三次重构。
2.添加功能时一并重构。
3.修补错误时,一并重构。
4.复审代码时,一并重构。

重构价值

程序有两面价值:[今天可以为你做什么]和[明天可以为你做什么]。大多数时候只关注今天想要程序做什么。不论是修复错误或是添加特性,我们都是为了让程序能力更强,让他在今天更有价值。
但是,系统今天的行为,只是整个软件的某一部分,如果没认识到这点,那么无法从事长期的编程工作。只为求完成今天的任务而采取的手法,而不能完成明天的任务,那么还是失败的。
我们希望程序:(1)容易阅读;(2)所有逻辑都只在唯一地点指定;
     (3)新的改动不会危及现有行为;(4)尽可能简单表达添加逻辑;


间接层和重构

重构往往把大型对象拆成数个小型对象,把大型函数拆成数个小型函数。但是间接层是一把双刃剑,每次把一个东西分成两份,你就
     需要多管理一个东西。如果某个对象委托(delegate)另一个对象,后者又委托另一个对象,程序会难以阅读。基于这个观点,我们希望尽量的减少间接层。
但是间接层确实存在很大的价值:
     (1)它允许逻辑共享。比如说一个子函数在两个不同的地点被调用,或superclass中的某个函数被所有subclasses共享。 (2)分开意图和实现。(3)将变化加以隔离。
     (4)将条件逻辑加以编码。

何时不该重构

(1)重构它还不如重新写一个来得简单
(2)如果项目已经接近最后期限,也应该避免重构

自我测试

程序员最多的时间并不是花在编写代码上。有些时间用来决定下一步做什么,有些时间花在设计上,最多的时间可能是用来调试。
     频繁的运行测试(极限编程的重要一环)能使你的项目更加不容易出错,或者说更快的找出错误。这就需要:确保所欲测试都完全自动化,
     让他们检查自己的测试结果。一整组测试就是一个强大的[臭虫]侦测器,能够大大缩减查找[臭虫]所需要的时间。考虑可能出错的边界条件,把测试精力集中在那里。



1. Duplicated Code(重复的代码)

代码重复几乎是最常见的臭味,不管你的经验多么丰富,它总是经常出现在项目中,作者提出的第一个准则就是Extract Method(110,提炼函数),这我想大家平时可能都会经常用到(我觉得本书不仅仅是告诉你很多不知道的,还把很多你知道的重构手法给出了适当的定义,即使他简单的不能再简单)。如果提炼动作可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。

当你运用Extract Method将相似部分和差异部分开时,构成单独一个函数,你可能发这些函数以相同的顺序执行类似的措施,但个措施实际上有所不同。这时候你可以使用Form Template Method(345,塑造模板函数)。回顾我维护过的一个项目,负责帮别人写几张报表,一共有七八张报表,数目也不是很多,每次生成的时候总会调用类似的方法,比如是设置背景色,标题,页眉,页脚等,当时采用的方法是,每个报表都复制粘贴相同的代码,设置一下不同的页眉或者角色就OK了,虽然觉得代码有改进的地方,但是当时并没有看到重构这本书,而且是别人的项目,我只是临时参与,所以也就没管那么多,现在发现确实可以使用Form Template Method重构当时的代码,增加可读性和可理解性。

如果两个毫不相关的classes内出现重复的代码,应该考虑对其中一个使用extract Class(149,提炼类),将重复的代码提练到一个独立的Class中,然后在另一个class内使用这个新class。

2.Long Method(过长函数)

程序愈长愈难以理解,如果能给程序起一个好名字,就可以通过名字了解函数的作用,根本不用去管代码里面写了什么。对于过长的函数我们通常才去extract method,但有时候过多的临时变量会让你的提炼方法有过长的参数列,使得可读性几乎没有提升,这时候可以采用Replac Temp With Query(120,查询替换临时变量),那么同一个class中都可以获得这份信息,对于某些赋值多次的临时变量可以使用Split Temporary Variable(128,剖析临时变量),针对每次赋值,创建一个独立的、对应的临时变量。对于赋值超过一次,既不是循环变量也不是一个集用临时变量时,针对每次赋值,创造一个独立的对应的临时变量,这里可以将变量申明为final,确保他只被赋值一次。

对于过长的参数列可以用Introduce Parameter Object(295,引入参数对象)或者Preserve Whole Object(288,保持对象完整,改传递对象的某些属性而传递整个对象),对于一个复杂一点的查询总是容易引入过长的参数,这时用这两个方法,再结合某些框架,将会得到非常简洁的代码,比如struts2的模型驱动。

如果这样还有过多的临时变量和参数,可以使用Replace Method with Method Object(135,以函数对象取代函数),将函数放进一个单独的对象中,再采用前面的方法。


对于提炼哪一段代码,很好的技巧就是:寻找注释,这通常是提炼函数的信号,就算只有一行代码,如果它需要注释来说明,那也值得将他提练到一个函数中去。
条件式和循环常常也是提炼的信号,条件式可以使用Decompose Conditional(238,分解条件式)从if else 中分别提炼出独立的函数。至于循环,应该将循环和其内的代码提炼到一个独立函数中。


3.Large  Class(过大类)

如果单一的Class做太多的事情,其内往往会出现太多的instance变量,同时也会照成Duplicated Code。如果Class内的数个变量有着相同的前缀或者字尾,就意味着,有机会把他们提练到某个组件内,例如Extract Subclass(330,提炼子类,Class中的某些特性只被某些实体用到),一个可能的信号就是,你的类中有一些属性充当标志位的作用:如果A,那么…..,如果B,那么…….,这时,你就可以考虑提炼子类,并运用Replace Conditional Polymorphism(225,运用多态取代条件式)

当然前面提到的Extract Class 和提炼函数等都是可以结合使用,这里作者还提到了一个技巧,就是先确定客户端如何使用类,然后运用Extract Interface(341,提炼接口)为每一种使用方式提炼出一个接口,如果某个class在不同环境下扮演截然不同的角色,使用interface就是个个好主意。可以针对每个角色提炼出相应的接口。将这种不同分离出来更加清晰,同时也更加容易看清楚系统的责任划分,当然公共的接口不能提供公共的代码,所以通常一个公共的借口会照成代码重复的臭味,这里有一个小原则,针对每一个公共的接口,总应该提供一个公共的实现类。


4.Long Parameter List(过长参数列)

过长参数列对程序的可读性确实会照成非常大的影响,而且更重要的是过长的参数通常都是开发初期的不合理设计,随着系统开发会不断增加参数或者去除参数,这种频繁的改变对调用者来说是非常讨厌的,而且容易照成错误,这时候,使用Introduce Parameter Object(295,引入参数对象),当然有时候你也可以使用Replace Parameter with method(292,以函数取代参数)和Preserve Whole Object(288,保持对象的完整)

5.Divergent Change(发散式变化)

如果因为某些外部事物的变化或者加入一个新的事物,那么这个类里面的几个方法就得改变,此时将改变的部分和不改变部分分为两个更好。
这个臭味通常很难发现,通常要有外部因素的时候你才会发现可以提炼的部分,但是问题不大,事不过三嘛


6.Shotgun Surgery

这个和Divergent Change有点类似,只不过这个是指Divergent Change一个class受多种变化的影响,而这里则是一个变化引发多个class相应的修改。我们可以使用Move Method(142,搬移函数)和Move Field(146,搬移值域)把所有需要改的地方移进同一个class。有时你将使用Inline Class(154,将类内联化)对于那些不再承担责任,就应该将类内联化。

7.Feature Envy(依恋情节)

这里面的一些依赖关系以及取舍则是仁者见仁智者见智,反正无论何时,move method和extracr method都是方便使用的。


8.Data Clumps(数据泥团)

前面提到的过长参数项就好像属于这一项的一个子集,前面提到的方法都是可用的

















分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics