《重构》代码的坏味道-笔记

869 阅读9分钟

摘录了《重构》中关于代码坏味道的笔记,发表出来备忘 + 分享,方便快速学习和回顾。

我们并不试图给你一个何时必须重构的精确衡量标准。从我们的经验看来,没有任何量度规矩比得上见识广博者的直觉。我们只会告诉你一些迹象,它会指出“这里有一个可以用重构解决的问题”。你必须培养自己的判断力,学会判断一个类内有多少实例变量算是太大、一个函数内有多少行代码才算太长。

神秘命名

命名是编程中最难的两件事之一,好的名字能节省未来用在猜谜上的大把时间。 改名不仅仅是修改名字而已。如果你想不出一个好名字,说明背后很可能潜藏着更深的设计问题。为一个恼人的名字所付出的纠结,常常能推动我们对代码进行精简。

重复代码

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

过长函数

函数越长,就越难理解。如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。据我们的经验,活得最长、最好的程序,其中的函数都比较短。 更好的阐释力、更易于分享、更多的选择都是由小函数来支持的。 让小函数易于理解的关键还是在于良好的命名。

我们遵循这样一条原则:

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

过长参数列表

刚开始学习编程的时候,老师教我们:把函数所需的所有东西都以参数的形式传递进去。这可以理解,因为除此之外就只能选择全局数据,而全局数据很快就会变成邪恶的东西。但过长的参数列表本身也经常令人迷惑。

全局数据

良药与毒药的区别在于剂量。有少量的全局数据或许无妨,但数量越多,处理的难度就会指数上升

全局数据仍然是最刺鼻的坏味道之一。全局数据的问题在于,从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底哪段代码做出了修改,想要找到出错的代码难于登天。

可变数据

可变数据的值可能在其他地方计算出来,对数据的修改经常导致出乎意料的结果和难以发现的bug,这就是一个特别刺鼻的坏味道。它不仅会造成困扰、bug和加班,而且毫无必要。 函数式编程建立在“数据永不改变”的概念基础上:如果要更新一个数据结构,就返回一份新的数据副本,旧的数据仍保持不变。

发散式变化

让程序变得更好:每当要对某个上下文做修改时,我们只需要理解这个上下文,而不必操心另一个。

一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。如果不能做到这一点,你就嗅出两种紧密相关的刺鼻味道中的一种了。

霰弹式修改

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

依恋情结

一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流,这就是依恋情结的典型情况。 所谓模块化,就是力求将代码分出区域,最大化区域内部的交互、最小化跨区域的交互。 解决:判断哪个模块拥有的此函数使用的数据最多,然后就把这个函数和那些数据摆在一起。

数据泥团

如两个类中相同的字段、许多函数签名中相同的参数等,这些总是绑在一起出现的数据真应该拥有属于它们自己的对象。 一个好的评判办法是: 删掉众多数据中的一项。如果这么做,其他数据有没有因而失去意义?如果它们不再有意义,这就是一个明确信号:你应该为它们产生一个新对象。

基本类型偏执

很多程序员不愿意创建对自己的问题域有用的基本类型,如钱、坐标、范围等。于是,我们看到了把钱当作普通数字来计算的情况、计算物理量时无视单位(如把英寸与毫米相加)的情况以及大量类似if (a < upper && a > lower)这样的代码。

重复的switch

重复的switch的问题在于:每当你想增加一个选择分支时,必须找到所有的switch,并逐一更新。多态给了我们对抗这种黑暗力量的武器,使我们得到更优雅的代码库。 如今的程序员已经更多地使用多态,switch语句也不再像15年前那样有害无益,很多语言支持更复杂的switch语句,而不只是根据基本类型值来做条件判断。

循环语句

从最早的编程语言开始,循环就一直是程序设计的核心要素。如今已有点儿过时。 现在,函数作为一等公民已经得到了广泛的支持

冗赘的元素

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

夸夸其谈通用性

用不上的装置只会挡你的路。

当有人说“噢,我想我们总有一天需要做这事”,并因而企图以各式各样的钩子和特殊情况来处理一些非必要的事情,这种坏味道就出现了。如果用不到,就不值得。

临时字段

在字段未被使用的情况下猜测当初设置它的目的,会让你发疯。

因为我们假定对象在所有时候都需要它的所有字段,所以这样的代码让人不易理解。

过长的消息链

一旦对象间的关系发生任何变化,客户端就不得不做出相应修改。

如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象……这就是消息链。 采取这种方式,意味客户端代码将与查找过程中的导航结构紧密耦合。

中间人

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

内幕交易

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

过大的类

如果想利用单个类做太多事情,其内往往就会出现太多字段。一旦如此,重复代码也就接踵而至了。 和“太多实例变量”一样,类内如果有太多代码,也是代码重复、混乱并最终走向死亡的源头。最简单的解决方案是把多余的东西消弭于类内部。

异曲同工的类

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

纯数据类

所谓纯数据类是指:它们拥有一些字段,以及用于访问(读写)这些字段的函数,除此之外一无长物。 这样的类只是一种不会说话的数据容器,它们几乎一定被其他类过分细琐地操控着。 你应该在别人注意到它们之前,立刻运用封装记录将它们封装起来。

被拒绝的遗赠

子类应该继承超类的函数和数据。但如果它们不想或不需要继承。按传统说法,这就意味着继承体系设计错误。 既然使用“传统说法”这个略带贬义的词,你就可以猜到,我们不建议你这么做,起码不建议你每次都这么做。 这也是一种坏味道,但十有八九这种坏味道很淡,不值得理睬。

注释

从嗅觉上说,注释不但不是一种坏味道,事实上它们还是一种香味呢。 我们之所以要在这里提到注释,是因为人们常把它当作“除臭剂”来使用。有的代码注释之所以存在是因为代码很糟糕

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

我们首先应该以各种重构手法把坏味道去除。完成之后我们常常会发现:注释已经变得多余了,因为代码已经清楚地说明了一切。