读书_修改代码的艺术

遗留代码就是那些没有编写相应测试的代码.没有编写测试的代码是糟糕的代码. 不管我们有多细心地去编写它们,不管它们有多漂亮、面向对象或封装良好,只要没有编写测试,我们实际上就不知道修改后的代码是变得更好了还是更糟了.反之, 有了测试, 我们就能够迅速、可验证地修改代码的行为.

 

1.修改软件

修改软件的四个主要起因:

(1)添加新特性:

(2)修正bug:

(3)改善设计:

(4) 优化资源使用.

在所有四种情况下,我们都想要改变某些功能、某些行为,但我们想要保持不变的地方则要多得多. 保留既有行为不变是软件开发中最具挑战性的任务之一.即使是在改变主要特性时,通常也有很多行为是必须保留不变的.

 

行为保持是一项巨大的挑战.在我们需要作出修改并保持行为时,往往伴随着相当大的风险.为了减小风险,我们要考虑下面这三个问题.

(1)我们要进行哪些修改?

(2)我们如何得知已经正确地完成了修改?

(3)我们如何得知没有破坏任何(既有的)东西?

 

2.带着反馈工作

对系统进行改有两种主要方式.它们分别被称为编辑并祈祷(edit and pray) 和覆盖并修改(coverand modify), 覆盖软件即意味着用测试来覆盖它.当对一段代码有一组良好的测试时,我们就可以放心对它进行修改,并快速检验出修改是好是坏.

 

测试可以用于检验正确性. 测试还可以"通过测试来检测变化". 用传统的术语来说,这叫做回归测试.   还可以TDD.

 

单元测试所应具备的品质:

(1)运行快:

(2)能帮助我们定位问题所在.

 

以下算法可以用于对遗留代码基进行修改:

(1)确定改动点:

(2)找出测试点;

(3)解依赖:

(4)编写测试;

(5)修改、重构.

 

3. 感知和分离

如果我们想要将测试安置到位,有两个理由去进行解依赖:感知和分离.

(1)感知:当我们无法访问到代码计算出的值时, 就需要通过解依赖来"感知"这些值.

(2)分离:当我们无法将哪怕一小块代码放入到测试用具中去运行时,就需要通过解依赖将这块代码"分离"出来.

可用的替身对象: fake, dummy, stub, mock.

 

4.接缝模型

 

接缝(seam ): 顾名忠义,就是指程序中的一些特殊的点,在这些点上你无需作任何修改就可以达到改动程序行为的目的.

 

预处理期接缝/连接期接缝/对象接缝/

 

激活点:每个接缝都有一个激活点,在这些点上你可以决定使用哪种行为.

 

当习惯了以"接缝之眼"来看待代码时,就能更容易看出如何测试某段代码以及如何组织新代码的结构以令其更具"测试友好性"

 

5.工具

自动化重构工具

Mockframework

单元测试用具xUnit

集成测试框架Fit/Fitnesse

 

 

6.时间紧迫,但必须修改

如果修改可以在一个方法中的一处地方以单块连续的语句序列出现,可以使用 新生方法,新生类,外覆方法,外覆类.

 

优点是:新旧代码被清楚地隔离开.

缺点是:当你用这些技术时,效果上等于暂时放弃了原方法以及它所属的类,也就是说你暂时不打算将它们置于测试之下和改善它们了,而只打算写一个新的方法来实现某个新功能. 虽然是在系统中添加已被测试的代码,然而除非你用测试覆盖了所有调用这些代码的代码,否则还是没有对这些代码的使用进行测试.

 

7.漫长的修改

那些由小块的、命名良好的、可理解的部件组成的系统对付起来更为容易.

解依赖可以减少开发周期的时滞(Lagtime)

 

8.添加特性

 

测试驱动开发使用的算法如下:

(1)编写一个失败测试用例.

(2)让它通过编译.

(3)让测试通过.

(4)消除重复.

(5)重复上述步骤.

 

消除重复,是非常重要的.我们可以通过诸如复制粘贴整块代码这样的方式来快速但粗暴地往既有代码中添加新特性, 但如果事后不消除重复代码的话,无异会带来麻烦和维护负担.

 

测试驱动开发与遗留代码

测试驱动开发的最有价值的一个方面是它使得我们可以在同一时间只关注于一件事情. 要么是在编码,要么是在重构,永远也不会在同一时刻做两件事情.有了测试作后盾, 就可以放心重构了.

 

在遗留代码的工作场景中,我们可以将测试驱动开发的算法稍微扩展一点:

(1)将想要修改的类置于测试之下。

(2)编写一个失败测试用例。

(3)让它通过编译。

(4)让测试通过(在进行这一步的过程中尽量不要改动既有代码)。

(5)消除重复。

(6)重复上述步骤。

 

差异式编程: 一种使用继承来往面向对象系统中添加特性的编程方式,常常可以用于将一个新特性快速添加进系统中.而编写用来驱动新特性的测试则可以在之后被用于将系统重构至更好的状态.

 

9.无法将类放入测试用具

 

我们会遇到的四种最为常见的问题

(1)无法轻易创建类的对象.

(2)当类位于测试用具中时, 测试用具无法轻易通过编译构建.

(3)我们需要用到的构造函数具有副作用.

(4)构造函数中有一些要紧的工作, 我们需要感知到它们.

 

令人恼火的参数

   在编写测试时,如果你发现某个对象需要的某参数难以构造,便可以考虑传递一个null.这样一来如果这个参数在测试运行的过程中被用到了的话, 代码就会抛出一个异常,而测试用具则会捕获这个异常.这时候,如果确实需要传递一个对象而不是null的话,再去构造这个对象并将它作为参数传递也不晚.

 

   传Null和接口提取是两种可以用来解决恼人的参数的途径. 但有时我们也可以使用另一个方案,如果一个参数类型中的问题依赖并不是被硬编码在其构造函数中的话,就可以使用子类化并重写方法(Subclassand Override Method.)来对付该依赖.

 

不到万不得己千万别在产品代码中传递null,现在有些库会期望你在某些情况下传递null ,这是无法控制的事情,但当编写自己的代码时,那么建议考虑使用空对象模式(NullObject  Pattern) .

 

隐藏依赖

  参数化构造函数(ParameterizeConstructor)

  提取并重写工厂方法

  提取并重写调用

  替换实例变量

 

恼人的全局依赖 --- 引入静态设置方法  Singleton

 

10.无法在测试用具中运行方法

隐藏的方法

  适当放松访问控制,   一定谨慎使用反射.

 

参数适配(Adapt Parameters) 引入参数接口

朴素化参数.

 

 

无法探知的副作用---方法提取,子类化并重写方法

 

命令/查询分离

 

11.修改时应当测谎哪些方法

推测代码修改所产生的影晌

影响草图---作图的关键是:为每个可能会被影响到的变量以及每个返回值可能改变的方法画一个单独的椭圆.这些变量可能来自同一个对象,也可能来自不同的对象.究竟属于何者并不重要,我们只需为每个会改变的东西画上一个椭圆,并从它们出发画一个箭头指向那些因它们的改变而在运行期改变的东西.

 

在同一地进行多处修改,

  是否应该将相关的所有类都解依赖?

 

  "退后一层"从而找到一个地点能够同时给多处修改编写测试.比如要对一系列私有方法进行修改,只需为某一个公有方法编写测试就行了.或者,要对某个对象所持有的一组互相协作的对象进行测试,我们只需测试前者的接口即可.采用这种方法不仅能够达到覆盖所作修改的目的,还能在代码重构方面提供更大的自由度;在不违反测试所限定的行为的前提下,测试覆盖之下的代码无论怎么改都不要紧.

 

拦截点( interception point) 如何寻找到最佳拦截点,即所谓的汇点(pinch point)

 

汇点是影响结构图中的隘口和交通要冲,在汇点处编写测试的好处就是,只需针对少数几个方法编写测试,就能够达到探测大量其他方法的改动的目的.

 

13.修改时应该怎样写测试

自动化测试是一个非常有用的工具,但这并非对寻找bug而言,至少并没有直接的关系.一般而言,自动化测试的任务是明确说明一个我们想要实现的目标,或者试图保持代码中某些既有的行为.在自然的开发流程中,属于前者的测试逐渐就会变成后者.

 

我把用于行为保持的测试称为特征测试(Characterizationtest) .特征测试刻画了一块代码的实际行为.特征测试描述了系统当前的实际行为.

以下是编写特征测试的几个步骤:

(1)在测试用具中使用目标代码块.

(2)编写一个你知道会失败的断言.

(3)从断言的失败中得知代码的行为.

(4)修改你的测试,让它预期目标代码的实际行为.

(5)重复上述步骤.

 

假设现在有一个类,我们想要知道应该测试哪些东西.第一件事就是要从较高的层面上来领会该类会做些什么.比如我们可以先针对能想到的最简单的行为来编写几个测试,然后把任务交给我们的好奇心,让好奇心领着我们往前走.下面是几个有益的启发式方法.

(1)寻找代码中逻辑复杂的部分.如果你不理解某块代码,可以考虑引入感知变量来刻画它.利用感知变量来确保代码中的某些特定的区域被执行到了.

(2)随着你不断发现类或方法的一个个职责,不时停下来把你认为可能出错的地方列一个单子.看看能不能编写出能够触发这些问题的测试.

(3)考虑你在测试中提供的输入.如果故意把输入的值变得极端化会出现什么情况呢?

(4)对于某个类的对象,有没有某些条件在它的整个生命周期当中都是成立的?通常这被人们称为不变式( invariant).尝试编写测试去验证它们.通常你可能需要重构才能够发现这些不变式.但重构往往能够给你带来关于"代码应该怎样"的新认识.

 

我们编写出来的用于刻画代码的测试是非常重要的.它们描述了系统实际的行为.

 

14.棘手的库依赖问题

每一处以硬编码方式来直接使用类库的地方其实都可以以接缝的形式来实现.有些库在给其中的具体类定义接口方面做得较好,而有些库则不仅做不到这点,还把具体类做成final或sealed的,又或者是具有些非虚的关键函数,让你没法在测试中"伪造"它们.遇到这类情况,你也只能给这种类写一个对应的外覆类了.

 

非虚方法使得往代码中引入感知和分离变得困难.

 

15.到处都是API 调用

到处都是库调用的系统比完全自己编写的系统还难对付.其首要的原因就是,对于这种系统你很难看出如何才能让代码的结构变得好起来,因为一眼望过去,到处都是API调用.看不到任何可以从中引出设计的东西.API密集的系统之所以难以对付的另一个原因就是我们并不拥有那些API.

找出代码的计算核心:这段代码到底做了什么? 

 

解决问题两个办法:

(1)剥离并外覆API (Skin and Wrap the API) ,.其实就是先编写能够尽量准确对应API的接口,然后再为库类创建外覆类.

(2)基于职责的提取.根据代码做事的步骤,  提取方法或划分类职责.

 

16.对代码的理解不足

注记/草图

如果阅读代码还是搞不清的话,你可以画草图并作一些注记.把最近看到的重要的地方写下来.如果你看到它们之间存在着某个联系,就在它们之间画一条线.

 

理解你的修改产生的影响

 

清单标注, 对长方法进行职责,块标注.  之后据此进行草稿式重构,方法/类提取…  没有测试进行重构是危险的,  但并不把修改提交至代码库,   这只是为了理解代码.

 

删除注释掉的/不用的代码.   也许它们真的以后会被用到,  去找版本库吧.

 

 

17.应用毫无结构可言

如果个团队不了解他们的代码的架构,后者就会变得越来越糟糕.那么,到底是什么东西阻碍了一个团队去了解他们的代码架构呢?

1.系统可能过于复杂,要想对它有一个整体认识需要花很长时间.

2.系统可能过于复杂,乃至于根本没有所谓的整体认识.

3.整个团队就像绷紧的弹簧一样,光顾着埋头解决一个又一个 的紧急情况,根本无暇顾及什么整体认识了.

 

架构师不是少数人所专有的,而必须是大家的,每一个接触代码的人,都应该了解架构,从架构师那里所学到的东西那儿获益.

 

 

"CRC"这一缩写表示"类( Class)"、"职责(Responsibility) "和"协作(Collaboration) ".每一个卡片都被标上对应类的类名、职责以及与它交互的类(协作类).如果你觉得某个职责并不属于某个类,可以把它从相应卡片上划掉,并写到另一个(它应属的那个)类的卡片上,或者新增一个类卡片.

 

"NakedCRC 气因为除了不是把东西写在卡片上之外.它几乎跟CRC一样.但它一般代表实例, 类此精简的实例图.

"Naked.CRC" 在使用的时候有两条原则·

(1)卡片代表实例,而非类;

(2)用叠在一起的卡片来表示" 一组实例".

 

 

在对付遗留代码时,我们一般都不愿看到系统里面再出现新的抽象了.在面对四五个类,其中每个都具有大约一千行代码的时候,除了竭力试图搞清哪些地方得修改之外,我想谁也不会愿意再去往里面塞两个新类.   但是类的存在本身就是提供抽象,  显式的抽象,  我们可以用数组表示一个班级的学生, 但用引入一个班级类将式逻辑变得清晰的多.

 

设计就是设计,不管它发生在开发周期中的哪一个环节.人们会犯的最糟糕的错误之一便是认为到了开发周期的某个阶段设计也就宣告死亡了.如果设计"己死"而人们却仍在修改的话,很可能就会把新的代码放在糟糕的地方,类会膨胀,因为没人会对引入新的抽象感到舒服.唉,如果你想把一个己是”遗留”的系统搞得更糟的话, 以上方式真是再合适不过了.

 

18.测试代码碍手碍脚

类命名约定

1目标类的类名衍生出其单元测试类的类名.就是往类名前加一个”Test”后缀.

2在测试中有时需要去伪造一个类的协作类(collaborator),这时一个有用的做法就是把这些伪类放在一个包或目录中.对于这类情况可以使用前缀”Stub”

3.测试子类(testing subclass) ,  可以使用前缀”Testing”

 

测试代码放在专门的目录下.

 

19.对非面向对象的项目,如何安全地对它进行修改.

使用连接期/宏预处理接缝,并努力将更大块的代码纳入测试之下.

 

对于过程式遗留代码而言,最好遵循一条原则,即宁可引入新的函数也不要把代码直接添加到旧代码中.因为至少我们可以给我们引入的新函数编写测试.

 

但类没有体现/分离职责和协作时,   用什么语言写的程序都是过程式的,   贫血的数据模型只相当于struct结构, 缺乏行为也就没有职责抽象.

 

20.处理大类

如果有一个庞大的类,那么第一个需要面对的问题就是:怎样修改才能不至于令已经糟糕的状况雪上加霜,可以使用的两个关键技术是新生类和新生方法.

 

对于庞大的类,一个关键的补救手段就是重构.将大类分解为一组小类是杳帮助的.然而最大的问题在于要搞清楚这些小类应该是什么样子的.指导原则是:单一职责原则(SRP)

 

职责识别

两个关键的问题就是"这个方法为什么在这儿?"它为这个类做了什么? " ,接着把这些方法分组,将具有相近目的的方法放在一起.

 

探索式方法#1: 方法分组

寻找相似的方法名.将一个类上的所有方法列出来(别忘了它们的访问权限),找出那些看起来是一伙的.

探索式方法#2 : 观察隐藏方法

注意那些私有或受保护的方法.大量私有或受保护的方法往往意味着一个类内部有另一个类急迫地想要独立出来.

"我怎么才能测试私有方法呢? "许多人花了大量时间试图避开这个问题,真正的答案是,如果你迫切需要测试一个私有方法,那么该方法就不应该是私有的,如果将它改为公有会带来麻烦,那么可能是因为它本就应属于另个独立的职责.它应该

在另一个类上.

 

探索式方法#3 寻找可以更改的决定寻找代码中的决定这里所说的"决定"并非指你正在做的决定,而是指已经作出的决定.比如代码中有什么地方(与数据库交互、与另一组对象交互,等等)采用了硬编码吗?你可以设想它们发生变化后的情况吗?

 

探索式方法#4 ,寻找内部关系

寻找成员交量和方法之间的关系"这个变量只被这些方法使用吗? "

 

探索式方法# 5 , 寻找主要职责

尝试仅用一句话来描述该类的职责.

 

单一职责原则的违反有两种形式.一是在接口层面违反,二是在实现层面违反.当一个类的接口呈现出负责多样事务的形态时,它就在接口层面违反了单一职责原则.然而我们最为关心的SRP违反还是实现层面的.简单地说就是,我们关心的是该类是否真的做了这些事情,还是仅仅将其委托给其他的类来完成.如果属于后者, 那么该类并不能算是一个巨大的单片类;而只不过是一大帮小类的" 前端只是一个更容易掌控的fascade.

 

探索式方法#6 ,当所有方法都行不通时,作一点草稿式重构

如呆实在很难看清一个类内部的职责,那么可以对它作一点草稿式重构.

 

探索式方法#7 关注当前工作

注意你目前手头正在做的事情.如国发现你自己正在为某件事情提供另一条解决方案,那么可能使意味着这里面存在一个应该被提取并允许替代的职责.

 

 

战略和战术问题.

 

战略

在识别出了所有独立的职责之后,我们该干些什么呢? 应该花上一周的时间把这些大类都重整一遍吗?应该把它们都化整为零吗?如果你有这个时间,那再好不过.但实际上这种可能性很小,而且这一过程也有一定风险.就我所见过的有关案例中,几乎毫无例外的,当一个团队进行一番大规模重构时,系统稳定性就会有一段时间的下跌,即便他们干得再仔细,一边编写测试一边重构,还是如此.当然,如果你们仍处在发布周期的早期,愿意承担这样的风险,且有时间,那么一次大规模重构也并非不可.只要记得别让那些bug搞得你不再想去做其他重构就是了.

 

战术

对于大多数遗留系统而言, 一开始你所能奢望的也就是能够在实现层面运用单一职责原则了:本质上这意味着从大类中提取出新类,然后把一些任务委托给这些新类.另一方面,在接口层面引入SRP就要麻烦一些了.类的客户代码需要改动,所以你得先给它们编写测试.不过还算好的是,实现层面SRP的引入有助于后面在接口层面的引入.

 

对一个庞大的类进行类提取通常是个好的开始.面临的放大危险便是野心过大.你可能已经对这个类做了一些草稿式重构,或是已经对该系统应该成什么样子建立起了一些其他的看法.但是请记住,你目前的系统结构在应用当中能够工作,它提供了软件的功能,它可能还没有准备好向前迈进. 要小心地向前改进.

 

 

21.需要修改大量相同的代码

引入助手方法

形成模板方法(重构)

 

 

22.要修改一个巨型方法,却没法为它编写测试

遇到庞大的方法时. 许多时候可以通过新生方法和新生类手法来避免对长方法进行重构.

 

巨型方法的种类:

1. 项目列表式方法: 可以将每个代码区段提取为一个单独的方法

2. 锯齿状方法(疯狂的缩进): 提取方法, 引入感知变量, 分解出方法对象, 卫语句,if/then/else

 

只提取你所了解的, 用来对付巨型方法的策略就是一开始迈小步.寻找那些我们可以不用测试也能放心提取出来的小块代码,然后添加测试来覆盖它们.

 

 

23.降低修改的风险.

单一目标的编辑,  不要同时考虑两件事,  不要试图同时添加新功能和重构…编程是关于同一时间只做一件事的艺术.

 

签名保持

重构通常意味着极具侵入性的编辑.我们将代码复制来复制去,并建立新类和新方法:从尺度上说这可比单单添加一两行代码大多了.一旦测试在手,我们便能够捕获在修改代码的过程中引入的许多错误.然而可惜的是,对于许多系统而言,要想让它足够可测试,以便能够对其进一步重构,就必须首先对它作一点重构.这种初始的重构(解依赖技术)注定要在没有测试的情况下完成,所以它们必须得是很保守的重构.    尽可能地采用签名保持手法,  减少风险.

 

依靠编译器

在静态类型的语言中,还能利用编译器的类型检查机制来找出需要修改的地方.

 

结对编程

 

24.当你感到绝望时

一旦掌控住了代码基,你们便会开始在里面不断制造出一块块良好的代码绿洲.在其中工作便成了快乐的体验.

 

 

25.解依赖技术

参数适配

分解出方法对象

定义补全(C/C++)

封装全局引用

暴露静态方法

提取并重写调用

提取并重写工厂方法

提取井重写获取方法

实现提取

接口提取

引入实例委托

  人们会因为各种各样的原因使用静态方法.其中最常见的原因便是为了实现单件模式.而另一个常见的原因是使用静态方法来创建实用类.实用类在许多设计中都是一眼就能看出来的.它们一般没有任何实例变量和实例方法.而是全部由静态方法和静态常量组成.大多数情况下,实用类也可以使用传统的带有实例数据和方法的类来完成工作.

   当静态方法中包含了一些没法或不想在测试的时候依赖的东西(静态粘着) 时, 委托实例方法便是较好的选择.

 

引入静态设置方法

   替换单件需要多花点工夫才行.首先是往单件类上添加一个静态的设置方法以便用它来替换单件实例,然后将构造函数设为受保护的.之后就可以对单件类进行子类化,创建一个全新的对象并将它传递给那个静态的设置方法了.这种做法可能会令你心里感到不安,因为你觉得单件类的保护给打破了,但是,别忘了,访问限制的目的在于防止错误,而我们编写测试的目的同样也是防止错误.而为了在这种情况下引入测试,我们不得己才用了强硬一点的手段.

 

连接替换

参数化构造函数

参数化方法

朴素化参数

特性提升

侬赖下推

换函数为函数指针(C/C++)

以获取方法替换全局引用

子类化并重写方法 是面向对象程序中解依赖的核心技术.许多其他的解依赖手法都是该手法的变种.

替换实例变量

模板重定义(C++)

 

 

术语表

修改点(change point) 需要往代码中引入修改的点.

 

特征测试(characterization test) 描述软件某部分的当前行为的测试,当你修改代码时能够用来保持行为.

 

耦合数(coupling count) 当一个方法被调用时传给它以及从它传出来的值的数目.如果该方法没有返回值,则稿合数就是它的参数数目.否则就是参数数目加1 0 如果你想要在没有测试的情况下提取出一个小方法的话,计算一下它的稿合数是很有意义的.

 

影响草图(effect sketch) 一张不大的手画草图,其作用是展示出哪些变量和方法返回值会被某个特定的修改所影响.在你试图寻找合适的测试地点时影响草图可以帮上忙.

 

伪对象(fake object) 在测试中伪装成一个类的合作者的对象.

 

特性草固(feature sketch) 一张不大的手画革图,展示了一个类中的方法是如何使用其他实例方法和变量的.在你试图决定如何分解一个大类时特性革图可以帮上忙.

 

自由函蚊(free function) 一个不属于任何类的函数.在C和其他过程式语言中,自由函数被简单地称为函数.而在C++中它们被称为非成员函数.Java和C# 中没有自由函数.

 

拦截点(interception point) 可以编写测试来感知某些条件的地点.

 

连接期接缝(link seam) 在连接期接缝处,你可以通过连接到另一个库来替换行为.在编译型语言中,可替换的东西包括产品库、DLL、程序集或JAR文件.其目的是为了解除依赖,或感知某些在测试期间可能会发生的条件.

 

仿对象( mock object) 在内部对条件进行断言的伪对象.

 

对象接缝(object seam) 在对象接缝处你可以通过替换一个对象为另一个对象来"更换"行为.在面向对象语言中,我们通常通过子类化产品代码中的类并重写其方法来实现这一点.

 

汇点(pinch point) 汇点是影响结构图中的隘口和交通要冲,在汇点编写测试的好处就是,只需针对少数几个方法编写测试,就能达到探测大量其他方法的改动的目的.

 

差异式编程(programming by difference) 一种使用继承来往面向对象系统中添加特性的编程方式,常常可以用于将一个新特性快速添加进系统中.而编写用来驱动新特性的测试则可以在之后被用于将系统重构至更好的状态.

 

接缝(seam) 接缝,顾名思义,就是指程序中的一些特殊的点,在这些点上你无需作任何修改就可以达到改动程序行为的目的.例如,对一个对象上的多态函数的调用就是一个接缝,因为你可以通过子类化该对象的类来让该调用具有另一种行为.

 

测试驱动开发(test-driven development,TDD) 测试驱动开发是一种开发过程,它包括编写失败测试用例,并一次一个地满足它们.在这么做的过程中,你通过重构来使代码尽量保持简单.使用TDD方法编写出来的代码默认就是测试覆盖的.

 

测试用具(test harness) 支持单元测试的软件.

 

测试子类(testing subclass) 为了访问被测试类而派生出来的子类.

 

单元测试(unit test) 单元测试的两个特点:一、运行时间小子十分之一秒, 二、足够小,从而当失败的时候能够帮你锁定问题.

 

 

附录 重构 

 

Smells:

  Duplicated Code

  Long Method

  Long Parameter List

  Divergent Change 一个class受多种变化的影响

  Shotgun Surgery 一种变化引发多个classes相应修改

  Feature Envy函数对某个class的兴趣高过对自己所处之host class的兴趣。这种孺慕之情最通常的焦点 便是数据。无数次经验里,我们看到某个函数,为了计算某值,从另一个对象那儿调用几乎半打的取值函数(getting method)。

  Data Clumps数据项(dataitems)就像小孩子:喜欢成群结队地待在一块儿。你常常可以在很多地方看到相同的三或四笔数据项:两个classes内的相同值域(field)、许多函数签名式(signature)中的相同参数。这些「总是绑在一起出现的数据」真应该放进属于它们自己的对象中

  Primitive Obsession

  Parallel Inheritance Hierarchies并行继承层次

  Lazy Class冗赘类

  Speculative Generality(夸夸其谈未来性)

  Temporary Field(令人迷惑的暂时值域)有时你会看到这样的对象:其内某个instance变量仅为某种特定情势而设。这样的代码让人不易理解,因为你通常认为对象在所有时候都需要它的所有变量。在变量未被使用的情况下猜测当初其设置目的,会让你发疯。(Replacemethod with  method object,method object只是为了提供调用函数的途径,class本身并无抽象意味)

  Message Chains(过度耦合的消息链)a.getb().getc().getd();

  Middle Man(中间转手人)

  Inappropriate Intimacy(狎昵关系)

  Alternative Classes with Different Interfaces(异曲同工的类)

  Incomplete Library Class(不完美的程序库类)(IntroduceForeign Method,Introduce Local Extension。)

  Data Class(纯稚的数据类)

  Refused Bequest(被拒绝的遗贈)

  Comments(过多的注释)

 

Refactoring:

  Replace Temp with Query(以查询取代临时变量)

  Replace Method with Method Object(以函数对象取代函数)

      

  Replace Data Value with Object(以对象取代数据值)----Primitive Obsession

  Change Value to Reference(将实值对象改为引用对象)

  Replace Array with Object(以对象取代数组)你有一个referenceobject(引用对象),很小且不可变(immutable),而且不易管理。

  Duplicate Observed Data(复制「被监视数据」)MVC

  Encapsulate Collection(封装群集)

 

  Separate Query from Modifier(将查询函数和修改函数分离)

  Replace Constructor with Factory Method

  Replace Error Code with Exception

  Replace Exception with Test

      

  Form Template Method

  Replace Inheritance with Delegation

  Replace Delegation with Inheritance

      

兴趣和热情可以弥补任何经验的不足.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值