Skip to content

Latest commit

 

History

History
111 lines (64 loc) · 9.1 KB

第3章 代码的坏味道.md

File metadata and controls

111 lines (64 loc) · 9.1 KB

第3章 代码的坏味道

3.1 Duplicated code (重复代码)
  1. 同一个类的两个函数还有相同的表达式;
  2. 两个互为兄弟的子类内含相同的表达式;
3.2 Long Method (过长函数)

每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。

如果函数内有大量参数和临时变量,会对函数提炼形成阻碍,如果尝试运用函数提取Extract Method(110),最终就会把许多参数和临时变量当做参数,传递给被提炼出来的新函数,导致可读性几乎没有提升。可以使用Replace Temp with query(120)来消除临时元素,Introduce Parameter Object(295)和Preserve Whole Object (288)将过长参数列表变简洁。如若还存在上述问题,就应考虑Replace Method with Method object(135)。

提炼代码技巧

  1. 寻找注释:他们通常能指出代码用途和实现手法之间的语义距离;就算只有一行代码,如果需要以注释来说明,也值得提炼到独立函数去;

  2. 条件表达式和循环常常也是提炼的信号。

3.3 Large Class (过大的类)
  1. 运用Extract Class(149)将几个变量一起提炼至新类内。提炼时应选择类内批次相关的变量,把他们放在一起。如果类内的数个变量有相同的前缀和字尾,就有机会把它们提炼到某个组件内。如果这个组件适合作为一个子类,使用Extract Subclass(330)比较简单。
  2. 和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的元呕吐。最简单的解决方案是把多余的东西消弥于类内部。
  3. 和“拥有太多实例变量”一样,一个雷如果拥有太多代码,往往也是和使用Extract Class(149)和Extract sub Class(330)。使用技巧:先确定客户端如何使用他们,然后运用Extract interface(341)为每一种使用方式提炼出一个接口。
3.4 Long Parameter List(过长参数列)

可使用对象技术改变这一情况;

注意:如果不希望造成“被调用对象”与“较大对象”之间的某种依赖关系,将数据从对象中拆解出来单独作为参数也很合情合理。如果参数列太长或变化太频繁,你就需要重新考虑依赖结构。

3.5 Divergent Change(发散式变化)
  1. 针对某一外界变化的所有相应修改,都只应该发生在某一类中,而这个新类内的所有内容都应该反映此变化。
3.6 Shotgun Surgery (霰弹式修改)

如果 每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是Shotgun Surgery。这种情况下你应该使用Move Method(142)和Move Field(146)把所有需要修改的代码放进同一个类中。通常使用Inline Class (154)把一系列相关行为放进同一个类。

3.7 Feature Envy (依恋情结)
  1. 有一种经典气味:函数对某个类的兴趣高过对自己所处类的兴趣。

  2. 一个函数往往会用到几个类的功能,他被安置何处的原则是:判断那个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。

3.8 Data Clumps(数据泥团)
  1. 首先找出这些数据以字段形式出现的地方,运用Extract Class(149)将它们提炼到一个独立对象中。然后将注意力转移到函数签名上,运用Introduce Parameter Object (295) 或Preserve Whole Object(288) 为他减肥。
  2. 一个好的评判方法是:删除众多数据中的一项。这么做,其他数据有没有因而失去意义。
3.9 Primitive Obsession (基本类型偏执)
  1. 如果你有一组应该总是被放在一起的字段,可运用Extract Class (149)。如果你在参数列中看到基本型数据,不妨试试Introduce Parameter Object(295)。如果你发现自己正从数组中挑选数据,可运用Replace Array With Object(186)。
3.10 Switch Statements (Switch 惊悚现身)
  1. 面向对象程序的一个最明显特征就是:少用 switch (case)语句。
  2. 大多数情况,一看到switch语句,就应该考虑使用多台来替换它。Switch语句常常根据类型码进行选择,我们需要的是“与该类型码相关的函数或类”,所以应该使用Extract Method(110)将switch语句提炼到一个独立函数中,再以Move method(142)将他搬移到需要多态性的那个类中,此时你必须决定是否使用 Replace Type Code With Subclasses(233)或Replace Type Code with State、Strategy(227)。
3.11 Parallel inheritance Hierarchies(平行继承体系)
  1. 在这种情况下爱,每当你为某一个类添加一个子类,必须也为另一个类相应增加一个子类。
  2. 消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。
3.12 Lazy Class (冗赘类)
  1. 如果一个雷的所得不值其身价,他就应该消失。
3.13 Speculative Generality(夸夸其谈未来性)
  1. 如果某个抽象类其实没有太大作用,请运用Collapse Hierarchy(344)。不要的委托可运用Inline Class(154)除掉。如果函数的某些参数未被用上,可对他实施Remove Parameter(277)。如果函数名称带有多余的抽象意味,应该对他实施Rename Method(273),让他现实一些。
3.14 temporary Field (令人迷惑的暂时字段)
  1. 如果类中有一个复杂算法,需要好几个变量,往往就可能导致坏味道Temporary Field的出现。这是可以利用Extract Class(149)把这些变量和其相关函数提炼到一个独立的类中,提炼后的新对象将是一个函数对象。
3.15 Message Chains(过度耦合的消息链)
  1. 如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象,...,这就是消息链。
  2. 通常更好的选择是:先观察消息链最终话得到的对象是用来干什么的,看看能否以Extract Method(110)把使用该对象的代码提炼到一个独立函数中,在运用Move Method(142)把这个函数推入消息链。如果这条链上的某个对象有多位客户打算航行此航线的剩余部分,就加一个函数来做这件事。
3.16 Middle Man(中间人)
  1. 对象的基本特征之一就是封装-对外部世界隐藏其内部细节。
  2. 过度运用委托:如果看到某个类接口有一般的函数都委托给其他类。应使用Remove Middle Man(160),直接和真正负责的对象打交道。如果这些Middle man还有其他行为,可以使用Replace Delegation with Inheritance(355)把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必负担那么多的委托动作。
3.17 Inappropriate Intimacy(狎昵关系)
  1. 过分狎昵的类必须拆散。继承往往造成过度亲密,因为子类对超累的了解总是超过后者的主观愿望。
3.18 Alternative Classes with Different Interfaces(异曲同工的类)
  1. 如果两个函数做同一件事,却有着不同的签名,请运用Rename Method(273)根据用途命名。但这往往不够,请反复运用Move Method(142)将某些行为移入类,直到两者的协议一致为止。如果你必须重复赘余地移入代码才能完成这些,获取可运用Extract Superclass (336) 为自己赎点罪。
3.19 Incomplete Library Class (不完美的类库)
  1. 复用常被视为对象的终极目的。如果只想修改库类的一两个函数,可以运用Introduce Foreign Method(162),如果想要添加一大堆额外行为,就得运用Introduce Local Extension(164)。
3.20 Data class (纯稚的数据类)
  1. 所谓Data Class是指:他们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。这些类早起可能拥有public 字段,果真如此你应该在别人注意到他们之前,运用Encapsulate Field(206)将它们封装起来。如果这些类内含有容器类的字段,应该检查他们是不是得到了恰当的封装。如果没有,就运用Encapsulate Collection(208)把他们封装起来。对于那些不该被其他类修改的字段,使用Remove Setting Method(300).
  2. 然后找出这些取值、设置函数被其他类运用的地点。尝试以move Method(142)把那些调用行为搬移到Data Class 来。如果无法搬移整个函数,就运用Extract Method(110)产生一个可被搬移的函数。不久之后就可以运用Hide Method(303)把这些取值/设置函数隐藏起来了。
3.21 Refused Bequest(被拒绝的遗赠)
  1. 子类应该继承超类的函数和数据。如果子类不想继承超类所有,可以为子类新建一个兄弟类。把子类用不到的函数推荐给兄弟类。
3.22 Comments(过多的注释)
  1. 当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
  2. 如果你不知道该做什么,这才是注释的良好运用时机。