Clean Code: Easy to read, maintain, understand and change through structure and consistency yet remains robust and secure to withstand performance demands.
# 什么代码应该被重构?
代码的好与坏不应当用个人好恶、“含混的代码美学”来表达。
即便是一些经典的程序设计原则,也有同样的问题。例如“高内聚低耦合”,尽管这是所有人都赞同的设计原则,但究竟什么样的代码呈现了“低内聚”、什么样的代码呈现了“高耦合” 、“低内聚”与“高耦合”是否总是同时出现、应该以何种办法提高内聚降低耦合……这些问题仍然是悬而未决的。
因此,对于真正在一线工作的人来说,“高内聚低耦合”很多时候就成了一句咒语,念完咒语后,呼唤出的其实还是每个人原本的编程习惯与风格,并不真正指导任何行为的改变。
而当我们去观察“低内聚高耦合”带来的问题时,事情就变得明朗了。“低内聚”会直接引发的现象是 霰弹式修改(Shotgun Surgery) :
霰弹式修改(Shotgun Surgery)
每当需要对某个事情做出修改时,你都必须在许多不同的类内做出许多小修改,那么就可以确定,你所面临的坏味道是霰弹式修改。当需要修改的代码分散在多处,你不但很难找到它们,也很容易忘记某个重要的修改。
而“高耦合”直接引发的现象则是有某种相似性、但又表现不同的 发散式变化(Divergent Change) :
发散式变化(Divergent Change)
如果某个类经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了。
“迪米特原则”也是常被提及的面向对象设计原则之一,然而知道这个名称是一回事,知道如何识别不符合迪米特原则的代码,则又需要更多的个人经验。《重构》把这个原则表述为两个非常直观的症状:“过长的消息链(Message Chains)”和“中间人(Middle Man)”。
如果你看到用户向一个对象请求另一个对象,而后者再次请求另一个对象,然后再请求另一个对象……这就是消息链。在实际代码中,你看到的可能是一长串取值函数,或者一长串临时变量。
人们可能过度运用委托。你也许会看到某个类接口有一半的函数都委托给其他类,这样就是过度运用。
只要能做到命名合理、没有重复、各个代码单元(类、函数等)体量适当、各个代码单元有明确且单一的职责、各个代码单元之间有恰当的交互,这就已经是质量相当高的代码了。
伴随着对具体症状的了解,对症的解决办法也变得明确。在《重构》第三章里非常明确地讲到:
- 对于“霰弹式修改”,解决的办法是使用“搬移函数”和“搬移字段”,把所有需要修改的代码放进同一个模块;
- 对于“发散式变化”,解决的办法是首先用“提炼函数”将不同用途的逻辑分开,然后用“搬移函数”将它们分别搬移到合适的模块;
- 对于“过长的消息链”,你应该使用“隐藏委托关系”;对于“中间人”,对症的疗法则是“移除中间人”,甚至直接“内联函数”。
# 培养对“坏味道”的判断力
当然,每位实践者仍然“必须培养出自己的判断力,学会判断一个类内有多少实例变量算是太大、一个函数内有多少行代码才算太长”。
就在最近,我看到某大厂的一位“代码委员会理事”在文章里说,某段代码“挺好的,长度没超过80行,逻辑比较清晰”。而在我看来,一个函数超过7行就已经是“太长”(这还是在考虑到Java语法比较啰嗦的前提下)。这就是不同实践者“自己的判断力”所体现的差异。
尽管从来没有明确指定对每个函数或类的代码行数要求,但“对象健身操”这篇文章(见于《ThoughtWorks文集》)提出的9项规则已经有非常明确的指向:
方法中只允许使用一级缩进;
不允许使用else关键字;
封装所有的原生类型和字符串;
……
2
3
4
在这样的规则约束下,写出一个超过10行的函数将是相当困难的(实际上在“规则6:保持实体对象简单清晰”中已经明确提出,每个类的长度不能超过50行)。
正如“对象健身操”这篇文章的作者Jeff Bay自己所说,这套“健身操”的意义在于:“在一个简单的项目里尝试一些比以前严格得多的编码标准……会迫使你更为严格地以面向对象的风格编写代码”,从而“以一种全新的方式思考你的代码”。
不过这得需要你刻意练习。正所谓“台上一分钟,台下十年功”,缺乏在受控环境下的刻意练习,很难通过工作中的自然积累提升判断力。
另外,对正确的代码构造足够熟悉,也是很重要的一个基本功,这个观点最早是Kent Beck的《实现模式》这本书中提到的。什么意思呢?
传说旧时民间古董店的学徒需要先在仓库里看真货,看得多了,见到假货时就会本能地提起警觉。对于代码也是一样:程序员需要熟悉正确的代码构造,在看到有问题的代码构造时才会本能地提起警觉。并且,“正确的代码构造”并非无穷无尽,实际上在单线程编程中,几十个常见的模式已经几乎能够完全覆盖所有场景。
Kent Beck在前言里说“这是一本关于‘如何编写别人能懂的代码’的书”,尽管他还谦虚地说这本书“不是模式书籍”,但实际上《实现模式》充分地展现了“模式”的本意:它提供了一整套“用代码表述意图”的模式语言,这套语言能让程序员在最短的时间内学会如何写出具有表现力的代码,并且自然而然地远离坏味道。
从一开始就以合理的方式编程,从而使坏味道不要出现,我想这才是负责任的程序员应该采取的工作方式。
当然,极限编程的各种实践,尤其是工程技术实践彼此紧密相关。例如自动化测试、持续集成、集体代码所有制的缺失,都会导致代码的坏味道更容易堆积。而从另一个角度来看,这些实践从任何一个切入,又都会自然地引导出其他相关的实践。
一位“知行合一”的程序员最终会发现,极限编程是唯一合理且有效的软件开发方法。最终,只有采用以可工作的软件为核心的软件开发方法,才能得到高质量的可工作的软件,这就是《敏捷宣言》第二句关于坏味道的终极答案。