Skip to content

重构 - 3. 代码的坏味道

🏷️ 《重构》

第 3 章介绍了何时应当重构?作者总结了一些常见的场景,但由于不可能给出一个精确的衡量标准,所以还需依赖开发者的经验来判断。
书中还给出了每种场景应该使用何种重构方法来解决,有兴趣的小伙伴建议买实体书来阅读。

1. 神秘命名(Mysterious Name

整洁代码最重要的一环就是好的名字,所以我们会深思熟虑如何给函数、模块、变量和类命名,使它们能清晰地表明自己的功能和用法。
命名是编程中最困难的两件事之一。
为一个恼人的名字所付出的纠结,常常能推动我们对代码进行精简。

2. 重复代码(Duplicated Code

一旦重复代码存在,阅读这些重复的代码时你就必须加倍仔细,留意其间细微的差异。
如果要修改重复代码,你必须找到所有的副本来修改。

3. 过长函数(Long Function

活的最长、最好的程序,其中的函数都比较短。
函数越长,就越难理解。
每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。

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

过长的参数列表本身经常令人迷惑。

5. 全局数据(Global Data

全局数据的问题在于,从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改。
良药和毒药的区别在于剂量。有少量的全局数据或许无妨,但数量越多,处理的难度就会指数上升。

6. 可变数据(Mutable Data

对数据的修改经常导致出乎意料的结果和难以发现的 bug。
在一处更新数据,却没有意识到软件中的另一处期望着完全不同的数据。

7. 发散式变化(Divergent Change

一旦需要修改,我们希望能够调到系统的某一点,只在该处作修改。
如果某个模块经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了。
每次修改应只关心一个上下文。

8. 霰弹式修改(Shotgun Surgery

霰弹式修改类似于发散式变化,但又恰恰相反。
如果每遇到某种变化,你都必须在许多不同的类内做出许多小修改,你所面临的坏味道就是霰弹式修改。
如果需要修改的代码散布四处,你不但很难找到它们,也很容易错过某个重要的修改。

9. 依恋情结(Feature Envy

所谓模块化,就是力求将代码分出区域,最大化区域内部的交互,最小化跨区域的交互。
但有时你会发现,一个函数跟另一个模块的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流,这就是依恋情结的典型情况。

10. 数据泥团(Data Clumps

你常常可以在很多地方看到相同的三四项数据:两个类中相同的字段、许多函数签名中相同的参数。
这些总是绑在一起的数据就是数据泥团,它们应该拥有属于它们自己的对象。

11. 基本类型偏执(Primitive Obsession

大多数编程环境都大量使用基本类型,即整数、浮点数和字符串等。
但我们发现一个很有趣的现象:很多程序员不愿意创建对自己的问题域有用的基本类型,如钱、坐标、范围等。

12. 重复的 SwitchRepeated Switch

重复的 Switch :在不同的地方反复使用同样的 switch 逻辑(可能是以 switch/case 语句的形式,也可能是以连续的 if/else 语句的形式)。
重复 switch 语句的问题在于:每当你想增加一个选择分支时,必须找到所有的 switch ,并逐一更新。

13. 循环语句(Loops

如今循环已经有点过时了。我们可以使用 以管道取代循环 来让这些老古董退休。

14. 冗赘的元素(Lazy Element

程序元素(如类和函数)能给代码增加结构,从而支持变化、促进复用或者哪怕只是提供更好的名字也好,但有时我们真的不需要这层额外的包装。

15. 夸夸其谈通用性(Speculative Generality

当有人说“噢,我想我们总有一天需要做这事”,并因而企图以各式各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。
这么做的结果往往造成系统更难理解和维护。

16. 临时字段(Temporary Field

临时字段:类内部的某个字段仅为某种特定情况而设。
这样的代码让人不易理解,因为通常认为对象在所有时候都需要它的所有字段。

17. 过长的消息链(Message Chains

如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象......这就是消息链。
在实际代码中你看到的可能是一长串取值函数或一长串临时变量。
采用这种方式,意味客户端代码将与查找过程中的导航结构紧密耦合。一旦对象间的关系发生任何变化,客户端就不得不做出相应的修改。

18. 中间人(Middle Man

对象的基本特征之一就是封装 -- 对外部世界隐藏其内部细节。封装往往伴随着委托。
但是人们可能过度运用委托。你也许会看到某个类的接口有一半的函数都委托给其它类,这样就是过度运用。

19. 内幕交易(Insider Trading

软件开发者喜欢在模块之间建起高墙,极其反感在模块之间大量交换数据,因为这会增加模块间的耦合。
在实际情况里,一定的数据交换不可避免,但我们必须尽量减少这种情况,并把这种交换都放到明面上来。

20. 过大的类(Large Class

如果想利用单个类做太多事情,其内往往就会出现太多字段。一旦如此,重复代码也就接踵而至了。

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

使用类的好处之一就在于可以替换:今天用这个类,未来可以换成用另一个类。
但只有两个类的接口一致时,才能做这种替换,。

22. 纯数据类(Data Class

所谓纯数据类是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。
纯数据类常常意味着行为被放在了错误的地方。
但也有例外,如纯数据对象被用作函数调用的返回结果。

23. 被拒绝的馈赠(Refused Bequest

子类应该继承超类的函数和数据。但如果子类复用了超类的行为(实现),却又不愿意支持超类的接口,坏味道就会变得很浓烈。
既然不愿意支持超类的接口,就不要虚情假意地糊弄继承体系。

24. 注释(Comments

并不是说不该写备注。
当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。


摘自:《重构:改善既有代码的设计》 -- 马丁·福勒(Martin Fowler