- 软件设计:从专业到卓越
- 张刚
- 2422字
- 2023-03-10 17:11:52
1.3 易于理解
代码是一种很特殊的产品。一旦它被写出来,就会被一遍遍地阅读。代码被反复阅读的原因是多样的,有时候是为了修复缺陷,有时候是为了理解其背后的原理或者实现了什么功能,还有时候是为了复用或者是在原来的基础上增加新的功能。
研究数据表明,代码在其生命周期中被阅读的时间,是编写代码所用时间的10倍。所以,如果代码编写得不容易理解,那么即使它实现了所需的功能,也很难被称为好的代码。《计算机程序的构造和解释》[3]的作者Harold Abelson有一个著名的观点:
计算机程序首先是用来给人读的,只是顺便用于机器执行。
——Harold Abelson
1.3.1 为什么代码难以理解
写出易于理解的代码不容易,写出不容易让别人理解的代码却是再容易不过。这是代码的天性使然。代码天生充满各种细节,每一行代码都有它的意义。尽管如此,高质量的软件设计一定会刻意且安全地隐藏细节,从而提升代码的可理解性。
不良代码充斥着细节和意外
虽然代码充满各种细节,但这绝对不意味着没有理解某一行代码,就不能理解整体代码的具体工作。这样的代码是一种灾难,因为它挑战的是人类的记忆能力和认知能力。好的代码一定会隐藏一切细节,并且是安全地隐藏这些细节,即没有“意外”。
我们的理解力依赖于抽象、层次化、刻意地忽略这些认知技巧。如果代码缺少封装,导致内部状态可以被任意修改,就必然会带来意外,影响抽象。我们还会“望文生义”:如果一个类的名字叫作订单,那我们一般不会从里面寻找和用户管理相关的信息,所以如果哪个工程师把用户管理相关的职责混进了订单类中,就给以后维护代码的人留下了陷阱。
不要有意外,不要强迫他人为细节阅读代码。这是优质代码结构的力量,也是优秀工程师的素养。换句话说,代码的设计结构应该最大化地降低理解负担、尽量减少阅读代码的必要性。例如,让工程师:
能通过阅读API声明去理解代码,就不要去阅读API是如何实现的;
能通过观察代码结构(如类名、包名、方法名)去理解代码,就不需要去阅读代码的内部实现;
能通过阅读直接理解代码,就不需要去阅读文档和注释。
范式或概念不一致
相信不少读者有过这样的经历:初学面向对象编程时,看到代码中有数百个类会觉得无从下手。同样,刚从面向对象编程转为函数式编程时,也会觉得比较别扭。这是因为不同编程范式之间的话语体系不一样。不仅缺乏语言范式的共识会影响代码的可理解性,在编程规范、实现惯例、架构模式或设计模式,以及技术框架的应用等方面也同样需要共识。
业务概念的共识也是一个重要方面。例如,在维护一个订餐系统时,代码维护人可能需要了解菜单这个功能是如何实现的,但他可能找不到代码,因为代码中出现了一个不同的名字:餐品列表。这就是陷阱了。菜单和餐品列表或许并无二致,也不存在哪个概念更为精确的问题,但是如果大家使用的是两种语言,彼此语言不通,那么代码的可理解性就会受到影响。
扩展阅读:面条代码和馄饨代码
面向对象编程在今天已是主流。但是,在面向对象刚刚兴起的时候,开发者社区中有些人认为面向对象并不容易理解,因为“类实在是太多了”。多态也让代码变得更复杂,如果不运行,都不知道代码走到哪里了。在这些人看来,反倒是面向过程的代码更容易理解,因为不论函数有多长,只要耐心阅读就能了解一切细节。产生这种感觉的原因就是他们试图用一种编程范式来解读另一种编程范式。
面条代码(spaghetti code)[4]描述的是一种不良的代码设计风格,常常出现在缺乏封装的过程式设计中。不良的设计中总会出现一些很长的函数,它们如同彼此“缠绕”的面条一样,所以称为面条代码。
面条代码把所有的业务逻辑都放在一起,尽管复杂,但是只要你有足够的耐心,一行一行地读下去,总能理解代码实现了什么,这是它的优势。而如果你习惯了面条代码,自然会养成一行一行阅读代码的习惯,带着这种思维模式去阅读面向对象的代码,往往会觉得找不到线索。
馄饨代码(ravioli code)[5]是面条代码的反面,它认为结构是编程世界中的主导因素。在面向对象程序中,程序的本质是对象和对象的协作。一般来说,好的对象式设计包含许多小而独立的类,每个类分别实现一个比较有限的功能,通过组合这些功能有限的类,就可以实现丰富多彩的功能。
面向对象代码强调的是“概念”,是结构和协作。如果把关注点从关心“如何实现”的流程,转移到关心“做什么”的对象结构和对象协作上,那么理解面向对象的代码时就会更加快捷。打个比方,你打开本书的目录,发现对“由外而内的设计”这一章特别感兴趣,就直接定位到这一章开始阅读,这便是面向对象的理解方法。如果你不关心目录,而是从第一页开始逐行阅读,逐行找到对代码质量的介绍,就更类似于面向过程的理解方法。
1.3.2 提升代码可理解性的关键
降低代码的复杂性,是提升代码可理解性的关键。正如著名的计算机科学家Tony Hoare所说:“我相信设计软件的方式有两种:一种是使软件足够简单而明显没有缺陷;另一种是使它足够复杂,以至于没有明显的(可被轻易发现的)缺陷。”Hoare表面上说的是缺陷,其本质就是代码的可理解性。
降低复杂性和许多编程实践密切相关,如更好的命名、一致的业务概念、更好的设计结构、尽量减少不必要的设计元素、减少重复、增加设计契约和测试的描述能力等。在本书的后续章节中,我们将依次展开这些概念。
此外,我想特别提一下与可理解性密切相关的非技术因素。经验表明,这比更多的技术技巧更有力量:一个程序员在编写代码的时候,是否思考过别人会如何阅读这段代码?又应该如何做,才能尽可能减少别人理解这段代码的成本?
我常常发现,凡是能真正把他人怎么阅读代码放在心上的软件工程师,即使一开始不具备非常好的设计技巧,随着时间的推移,也能很快学会这些技巧。心中有他人是非常重要的意识和素养。
有一些手段可以增强这种意识,例如,在极限编程中有一个结对编程的实践。在结对编程实践中,由于是两个人一起编程,所以他们需要随时考虑对方能否理解当前的代码。这会不断增强“代码将要被其他人阅读”的意识,迫使自己选择更合适的命名、简化设计结构、增加必要的注释等,从而有效提升代码的可理解性。