你代码的异味是故意的还是不小心?是故意的!
一、代码竟会有“气味”
食物在腐烂之际,会散发出异味,提醒人食物已经坏掉了,需要处理。同样,如果代码中某处出现了问题,也会有一些症状。这些症状,被称之为“代码异味”(Code smell,也译作“代码味道”)。与食物腐败发出的味道不同的是,代码异味并非真正的气味,而是一种“暗示”,暗示我们代码可能有问题,提示程序员需要对项目设计进行更进一步的查看。
代码异味一词最初是由 Kent Beck 在帮助 Martin Fowler 在编写《重构:改善既有代码的设计》一书时创造的。Martin Fowler 对代码异味的定义是:代码异味是一种表象,它通常对应于系统中更深层次的问题。
代码异味的产生原因跟厨师的“清洗过程中故意保留”不一样,它更多地并非刻意为之,创造者也未必“品尝”过自己所写的代码,它更多地是由于设计缺陷或不良编码习惯而导致的不良代码症状。
这种异味也并非来自一种有据可查的标准,更多的是来自程序员的直觉。尤其是经验丰富和知识渊博的程序员,他们无需思考,只要通过查看代码或一段设计就可以立马对这个代码质量产生这种“感觉”,能对代码设计的优劣有一个大致的判断。这有点类似我们英语学到一定程度后,即便不能完全看懂文章,但凭借语感也能选出正确答案。
二、 代码异味的影响
对于代码异味的出现我们其实无需过度紧张,因为在整个程序中代码异味是无处不在的。
一般情况下,有“异味”的代码也依旧能运行得很好。只是倘若重视不够,没有适当地维护或改进代码,代码质量就会下降,系统也会开始变得难以维护和扩展,同时也会增加技术债务。这就像做出有异味的九转大肠的的小胖厨师,在前期准备中对评委的建议置若罔闻,一意孤行,做出来的菜连自己都难以下咽。
所以团队应尽可能地做有质量的代码,减少甚至避免这些问题,产生高效益的成果。
三、 如何辨别代码异味
代码是否存在代码异味,通常是靠程序员的主观判断,但由于语言、开发者、开发理论的不同,对代码异味的判断也会存在差异。
所以要想更精准地识别代码异味,获得更高的代码质量,程序员需要大量的实践和经验。不过,前辈们总结的经验也可以让我们少走一些弯路。Martin Fowler 在《重构:改善既有代码的设计》一书中,列举了最常见的 24 种代码异味,可以帮助我们轻松识别,便于处理和改善它们:
1) 过大的类(Large Class)
一个类包含许多字段、方法或者代码行,并逐渐变得臃肿。
2) 数据泥团(Data Clumps)
代码的不同部分包含了相同的变量组,且这些数据总是绑在一起出现。
3) 过长参数列表(Long Parameter List)
指一个方法的参数超过了三个或四个。出现这种情况一般是将几种类型的算法合并到一个方法之后。
4) 基本类型偏执(Primitive Obsession)
创建一个原始字段比创建一个全新的类要容易得多,所以对于具有意义的业务概念如钱、坐标、范围等,很多程序员不愿意进行建模,而是使用基本数据类型进行表示,进而导致代码内聚性差、可读性差。
5) 神秘命名(Mysterious Name)
在编程中,命名是一件非常恼人的事情。一些可能只有自己看懂的命名,无疑加大了代码可读性的难度,有时甚至自己也会忘记这些命名的含义。
6) 重复代码(Duplicated Code)
这几乎是最常见的异味。当多个程序员同时处理同一程序的不同部分时,通常会发生这种情况。
7) 过长的函数(Long Function)
根据 Martin Fowler 的经验,通常活得最长、最好的程序,其中的函数都比较短。函数越长,就越难理解。
8) 全局数据(Global Data)
这是一个非常可怕且刺鼻的异味代码。因为从代码库的任何一个角落都可以修改它,而且没有任何机制可以探测出到底是哪段代码做出了修改。全局数据造成一次又一次的诡异 Bug,让我们很难找出出错的代码。
9) 可变数据(Mutable Data)
如果可变数据的变量的作用域越大, 越容易出现问题。变量是可以更改的,但我们可能不知道是哪里改变了它。
10) 发散式变化(Divergent Change)
是指一个类受到多种变化的影响。
11) 霰弹式修改(Shotgun Surgery)
是指一种变化引发多个类相应修改。
12) 依恋情结(Feature Envy)
一个类使用另一个类的内部字段和方法的数据多于它自己的数据。
13) 重复的 Switch(Repeated Switch)
在不同的地方反复使用 switch 逻辑。这带来的问题就是当我们想要增加一个选择分支时,就必须找到所有的 switch,并逐一更新。
14) 循环语句(Loops)
在编程语言中,循环一直是程序设计的核心要素。在《重构》中,Martin Fowler 认为它是一种代码异味,因为他们觉得如今的循环已经有点过时了。他们提出“以管道取代循环”,这样可以帮助我们更快看清被处理的元素以及处理它们的动作。
15) 冗赘的元素(Lazy Element)
这是几乎无用的组件。我们在设计代码时有时为了未来的功能设计出“预备”代码,但实际上从未实现;又或者这个类本来有用但随着重构,越来越小,最后只剩下一个函数。无论哪种,它们都是冗赘无用的。
16) 推测的通用性(Speculative Generality)
是指为了“以防万一”,支持预期的未来功能,但这些功能并未被实现,这些类、方法、字段或参数也从未被使用,结果导致代码变得难以理解和支持。
17) 临时字段(Temporary Field)
创建临时字段以用于需要大量输入的算法。但这些字段仅在算法中使用,其余时间不使用。
18) 过长的消息链(Message Chains)
当客户端请求另一个对象,该对象又请求另一个对象,依此类推时,就会出现过长的消息链。这些链意味着客户端依赖于类结构的导航。一旦发生更改,客户端也要跟着修改。
19) 中间人(Middle Man)
指一个类只执行一个动作,但将工作委托给另一个类,这种委托属于过度委托。该类也可能只是一个空壳,只负责委托且只有一件事。
20) 内幕交易(Insider Trading)
指模块之间大量地交换数据,增加模块之间的耦合。
21) 异曲同工的类(Alternative Classes with Different Interface)
是指两个类执行了相同的功能但具有不同的方法名称。
22) 纯数据类(Data Class)
指包含字段和访问它们的粗略方法(getter 和 setter)的类。这些只是其他类使用的数据容器。这些类不包含任何附加功能,并且不能独立操作它们拥有的数据。
23) 被拒绝的遗赠(Refused Bequest)
指如果子类复用了超类的行为,但又不愿意支持超类的接口的情况。
24) 注释(Comments)
程序员将其作为一种“除臭剂”使用情况下的行为。比如:一段代码有着长长的注释,但这段长注释的存在是因为代码很糟糕。
四、 如何对代码“除臭”
1)重构
上述代码异味没有优先级一说,所以对于程序员而言,只能依靠直觉和经验去决定是否需要重构。
重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。是实现敏捷性的最重要的技术因素之一。是程序员根据已识别出的气味然后将代码分成更小的部分的过程,再决定要么删除它们,要么用更好的代码替换它们,如此循环重复这个过程,直到异味消失,这样可能会提高代码质量并让代码变得更具简单性、灵活性和可理解性。
2)使用代码检测工具
识别和消除代码异味是一个令人厌烦且不确定的过程,而且也不可能手动查找到和删除掉所有异味,尤其是面对一个有着上千行异味的代码的时候。所以使用一些代码检测工具可以辅助我们进行快速大量地审查,帮助我们节约时间来做更为重要的工作,比如能专注于代码高层面的设计原则问题。
好了,关于代码异味的知识,算是讲了个清楚,那么让我们相约下一次代码评审吧!
评论