- 同一个类的两个函数还有相同的表达式;
- 两个互为兄弟的子类内含相同的表达式;
每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。
如果函数内有大量参数和临时变量,会对函数提炼形成阻碍,如果尝试运用函数提取Extract Method(110),最终就会把许多参数和临时变量当做参数,传递给被提炼出来的新函数,导致可读性几乎没有提升。可以使用Replace Temp with query(120)来消除临时元素,Introduce Parameter Object(295)和Preserve Whole Object (288)将过长参数列表变简洁。如若还存在上述问题,就应考虑Replace Method with Method object(135)。
提炼代码技巧
寻找注释:他们通常能指出代码用途和实现手法之间的语义距离;就算只有一行代码,如果需要以注释来说明,也值得提炼到独立函数去;
条件表达式和循环常常也是提炼的信号。
- 运用Extract Class(149)将几个变量一起提炼至新类内。提炼时应选择类内批次相关的变量,把他们放在一起。如果类内的数个变量有相同的前缀和字尾,就有机会把它们提炼到某个组件内。如果这个组件适合作为一个子类,使用Extract Subclass(330)比较简单。
- 和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的元呕吐。最简单的解决方案是把多余的东西消弥于类内部。
- 和“拥有太多实例变量”一样,一个雷如果拥有太多代码,往往也是和使用Extract Class(149)和Extract sub Class(330)。使用技巧:先确定客户端如何使用他们,然后运用Extract interface(341)为每一种使用方式提炼出一个接口。
可使用对象技术改变这一情况;
注意:如果不希望造成“被调用对象”与“较大对象”之间的某种依赖关系,将数据从对象中拆解出来单独作为参数也很合情合理。如果参数列太长或变化太频繁,你就需要重新考虑依赖结构。
- 针对某一外界变化的所有相应修改,都只应该发生在某一类中,而这个新类内的所有内容都应该反映此变化。
如果 每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery。这种情况下你应该使用Move Method(142)和Move Field(146)把所有需要修改的代码放进同一个类中。通常使用Inline Class (154)把一系列相关行为放进同一个类。
-
有一种经典气味:函数对某个类的兴趣高过对自己所处类的兴趣。
-
一个函数往往会用到几个类的功能,他被安置何处的原则是:判断那个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。
- 首先找出这些数据以字段形式出现的地方,运用Extract Class(149)将它们提炼到一个独立对象中。然后将注意力转移到函数签名上,运用Introduce Parameter Object (295) 或Preserve Whole Object(288) 为他减肥。
- 一个好的评判方法是:删除众多数据中的一项。这么做,其他数据有没有因而失去意义。
- 如果你有一组应该总是被放在一起的字段,可运用Extract Class (149)。如果你在参数列中看到基本型数据,不妨试试Introduce Parameter Object(295)。如果你发现自己正从数组中挑选数据,可运用Replace Array With Object(186)。
- 面向对象程序的一个最明显特征就是:少用 switch (case)语句。
- 大多数情况,一看到switch语句,就应该考虑使用多台来替换它。Switch语句常常根据类型码进行选择,我们需要的是“与该类型码相关的函数或类”,所以应该使用Extract Method(110)将switch语句提炼到一个独立函数中,再以Move method(142)将他搬移到需要多态性的那个类中,此时你必须决定是否使用 Replace Type Code With Subclasses(233)或Replace Type Code with State、Strategy(227)。
- 在这种情况下爱,每当你为某一个类添加一个子类,必须也为另一个类相应增加一个子类。
- 消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
- 如果一个雷的所得不值其身价,他就应该消失。
- 如果某个抽象类其实没有太大作用,请运用Collapse Hierarchy(344)。不要的委托可运用Inline Class(154)除掉。如果函数的某些参数未被用上,可对他实施Remove Parameter(277)。如果函数名称带有多余的抽象意味,应该对他实施Rename Method(273),让他现实一些。
- 如果类中有一个复杂算法,需要好几个变量,往往就可能导致坏味道Temporary Field的出现。这是可以利用Extract Class(149)把这些变量和其相关函数提炼到一个独立的类中,提炼后的新对象将是一个函数对象。
- 如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象,...,这就是消息链。
- 通常更好的选择是:先观察消息链最终话得到的对象是用来干什么的,看看能否以Extract Method(110)把使用该对象的代码提炼到一个独立函数中,在运用Move Method(142)把这个函数推入消息链。如果这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个函数来做这件事。
- 对象的基本特征之一就是封装-对外部世界隐藏其内部细节。
- 过度运用委托:如果看到某个类接口有一般的函数都委托给其他类。应使用Remove Middle Man(160),直接和真正负责的对象打交道。如果这些Middle man还有其他行为,可以使用Replace Delegation with Inheritance(355)把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。
- 过分狎昵的类必须拆散。继承往往造成过度亲密,因为子类对超累的了解总是超过后者的主观愿望。
- 如果两个函数做同一件事,却有着不同的签名,请运用Rename Method(273)根据用途命名。但这往往不够,请反复运用Move Method(142)将某些行为移入类,直到两者的协议一致为止。如果你必须重复赘余地移入代码才能完成这些,获取可运用Extract Superclass (336) 为自己赎点罪。
- 复用常被视为对象的终极目的。如果只想修改库类的一两个函数,可以运用Introduce Foreign Method(162),如果想要添加一大堆额外行为,就得运用Introduce Local Extension(164)。
- 所谓Data Class是指:他们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这些类早起可能拥有public 字段,果真如此你应该在别人注意到他们之前,运用Encapsulate Field(206)将它们封装起来。如果这些类内含有容器类的字段,应该检查他们是不是得到了恰当的封装。如果没有,就运用Encapsulate Collection(208)把他们封装起来。对于那些不该被其他类修改的字段,使用Remove Setting Method(300).
- 然后找出这些取值、设置函数被其他类运用的地点。尝试以move Method(142)把那些调用行为搬移到Data Class 来。如果无法搬移整个函数,就运用Extract Method(110)产生一个可被搬移的函数。不久之后就可以运用Hide Method(303)把这些取值/设置函数隐藏起来了。
- 子类应该继承超类的函数和数据。如果子类不想继承超类所有,可以为子类新建一个兄弟类。把子类用不到的函数推荐给兄弟类。
- 当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
- 如果你不知道该做什么,这才是注释的良好运用时机。