在作者的工作经历中,每当同事评论项目代码质量的时候,作者听到最多的评论是“代码 写得很烂”或“代码写得很好”。作者认为,用“好”“烂”这样的字眼来描述代码质量是非常 笼统的。当作者询问代码到底“烂”在何处或“好”在哪里时,尽管大部分同事都能简单地罗列几个“烂”的方面或好的方面,但他们的回答往往都不够全面,知识点零碎, 也无法切中要害。
当然,也有一些软件工程师对如何评价代码质量有所认识,如认为好代码是易扩展、易读、 简单、易维护的,等等,但他们对于这些评价的理解往往只停留在表面上,对于诸多更加深入 的问题, 如“怎么才算可读性好?什么样的代码才算易扩展、易维护?可读、可扩展与可维护 之间有什么关系?可维护中的‘维护’两字该如何理解?”,等等,他们并没有太清晰的认识。
实际上,对于代码质量的描述,除“好”“烂”这样比较简单、笼统的描述方式以外,还 有很多语义丰富、专业和细化的描述方式,如下所示:
灵活性(flexibility)、可扩展性(extensibility)、可维护性(maintainability)、可读性(readability)、 可理解性(understandability)、易修改性(changeability)、可复用性(reusability)、可测试性(testability)、模块化(modularity)、高内聚低耦合(high cohesion loose coupling)、高效(high effciency)、高性能(high performance)、安全性(security)、兼容性(compatibility)、易用 性(usability)、简洁(clean)、清晰(clarity)、简单(simple)、直接(straightforward)、少 即是多(less code is more)、文档详尽(well-documented)、分层清晰(well-layered)、正确 性(correctness 、bug free)、健壮性(robustness)、鲁棒性(robustness)、可用性(reliability)、 可伸缩性(scalability)、稳定性(stability)和优雅(elegant)等。
面到如此多的词汇,我们到底应该使用哪些词汇来描述一段代码的质量呢?
实际上,我们很难通过其中的某个或某几个词汇来全面地评价代码质量,因为这些词汇是 从不同角度描述代码质量的。例如,在评价一个人的时候,我们往往通过多个方面进行综合评 价,如性格、能力等,否则,对一个人的评价可能是片面的。同样,对于代码质量,我们也需 要综合多种因素进行评价,不应该从单一的角度去评价。例如,一段代码的可扩展性很好,但 可读性很差,那么,我们不能片面地认为这段代码的质量高。
注意,不同的评价角度并不是完全独立的,有些之间存在包含关系、重叠关系等,或者可 以互相影响。例如,代码的可读性和可扩展性好,可能意味着代码的可维护性好。而且,各种 评价角度不是“非黑即白”。例如,我们不能简单地将代码评价为可读或不可读。如果用数字 来量化代码的可读性,那么应该是一个连续的区间值,而非 0 、 1 这样的离散值。
不过,我们真的可以客观地量化一段代码的质量吗?答案是否定的。对一段代码质量的评 价,常常带有很强的主观性。例如,对于什么样的代码才算是可读性好,每个人的评判标准都 不一样。
正是因为代码质量评价的主观性,使得这种主观评价的准确度与软件工程师自身的经验有 极大的关系。软件工程师的经验越丰富,给出的评价往往越准确。形成对比的是,资历较浅的 软件工程师常常觉得没有一个可量化的评价标准作为参考,很难准确判断一段代码的质量。如 果无法辨别代码写得好或坏,那么,即使写再多的代码,编码能力也可能没有太大提高。
在仔细阅读前面罗列的代码质量评价标准之后,读者会发现,有些词汇过于笼统、抽象, 而且偏向于对整体的描述,如优雅、好、坏、整洁和清晰等;有些过于注重细节、偏重方法 论,如模块化、高内聚低耦合、文档详尽和分层清晰等;有些可能并不仅仅局限于编码,与架 构设计等也有关系,如可伸缩性、可复用性和稳定性等。
为了读者有重点地进行学习,作者挑选了7 个常用且重要的评价标准来详细讲解,包括可 维护性、可读性、可扩展性、灵活性、简洁性、可复用性和可测试性。
1.可维护性(maintainability)
对于代码开发,“维护”无外乎修改 bug、修改旧的代码和添加新的代码等。“代码易维护” 是指, 在不破坏原有代码设计、不引入新的 bug 的情况下, 能够快速修改或添加代码。“代码 不易维护”是指,修改或添加代码需要冒极大的引入新 bug 的风险,并且需要很长的时间才能 完成。
对于一个项目,维护代码的时间可能远远大于编写代码的时间。软件工程师可能将大部分 时间花在修复 bug、修改旧的功能逻辑和添加新的功能逻辑之类的工作上。因此,代码的可维 护性就显得格外重要。
对于维护、易维护和不易维护这 3 个概念,我们不难理解。不过,对于实际的软件开发,更重要的是需要清楚如何判断代码可维护性的高低。
实际上,可维护性是一个难以量化、偏向对代码整体进行评价的标准,它类似之前提到的 “好”“坏”“优雅”之类的笼统评价。代码的可维护性高低是由很多因素共同作用的结果。代 码简洁、可读性好、可扩展性好,往往就会使得代码易维护。更深入地讲,如果代码分层清 晰、模块化程度高、高内聚低耦合、遵守基于接口而非实现编程的设计原则等,就可能意味着 代码易维护。除此之外,代码的易维护性还与项目的代码量、业务的复杂程度、技术的复杂程 度、文档的全面性和团队成员的开发水平等诸多因素有关。
2.可读性(readability)
软件设计专家 Martin Fowler 曾经说过:“Any fool can write code that a computer can understand. Good programmers write code that humans can understand. ”(任何人都可以编写计算机能理解的 代码, 而好的程序员能够编写人能理解的代码。)在 Google 内部, 有一个称为“Readability” 的认证。只有拿到这个认证的软件工程师,才有资格在 Code Review 的时候批准别人提交的代 码。可见,代码的可读性有多么重要,毕竟,代码被阅读的次数有时候远远超过被编写和执行 的次数。
代码的可读性如此重要,在编写代码的时候,我们要时刻考虑代码是否易读、易理解。代 码的可读性在很大程度上会影响代码的可维护性,因为无论是修复 bug 还是添加 / 修改功能代 码,我们首先要读懂代码。如果我们对代码一知半解,就有可能因为考虑不周而引入新 bug 。
既然代码的可读性如此重要,那么我们如何评判一段代码的可读性呢?
我们需要查看代码是否符合代码规范,如命名是否达意、注释是否详尽、函数长度是否合 适、模块划分是否清晰, 以及代码是否“高内聚、低耦合”等。除此之外,Code Review 也是 一个很好的测试代码可读性的手段。如果我们的同事可以轻松地读懂我们写的代码,往往能够 说明我们的代码的可读性不差;如果同事在读我们写的代码时,有很多疑问,那么可能在提示 我们,代码的可读性存在问题,需要重点关注。
3.可扩展性(extensibility)
代码的可扩展性是指在不修改或少量修改原有代码的情况下,能够通过扩展方式添加新功 能代码。换句话说,代码的可扩展性是指在编写代码时预留了一些功能扩展点,我们可以把新 功能代码直接插入扩展点,而不会因为添加新的功能代码而改动大量的原始代码。可扩展性也 是评价代码质量的重要标准。代码的可扩展性表示代码应对未来需求变化的能力。与代码的可 读性一样,代码是否易扩展也在很大程度上决定了代码是否易维护。
4.灵活性(flexibility)
灵活性也可以用来描述代码质量。例如, 我们经常会听到这样的描述:“代码写得很灵 活”。那么,我们如何理解这里提到的“灵活”呢?
尽管很多人用“灵活”描述代码质量,但实际上,“灵活”是一个抽象的评价标准,给“灵活”下定义是很难的。不过,我们可以想一下,我们在什么情况下才会说代码写得很灵活呢?
作者罗列了 3 种场景,帮助读者理解什么是代码的灵活性。
1)当我们添加新功能代码时,由于原有代码中已经预留了扩展点,因此,我们不需要修 改原有代码,只需要在扩展点上添加新代码。这个时候,我们除可以说代码易扩展以外,还可 以说代码写得很灵活。
2)当我们要实现一个功能时,如果原有代码中已经抽象出了很多位于底层且可复用的模 块、类等,那么我们可以直接使用。这个时候,我们除可以说代码易复用以外,还可以说代码 写得很灵活。
3)当我们使用某个类时,如果这个类可以应对多种使用场景,满足多种不同需求,那么, 我们除可以说这个类易用以外,还可以说这个类设计得很灵活或代码写得很灵活。
从上述场景来看,如果一段代码易扩展、易复用,或者易用,我们一般可以认为这段代码 写得很灵活。因此,“灵活”的含义宽泛,很多场景都可以使用。
5.简洁性(simplicity)
有一条非常著名的设计原则,大部分读者应该都听过,那就是 KISS 原则:“Keep It Simple ,Stupid”。该原则的意思是“尽量保持代码简单”。代码简单、逻辑清晰往往意味着代 码易读、易维护。在编写代码的时候,我们往往会把“简单、清晰”原则放到首位。
不过,很多编程经验不足的程序员会觉得,简单的代码没有技术含量,喜欢在项目中引入 一些复杂的设计模式,觉得这样才能体现自己的技术水平。实际上,思从深而行从简,真正的 编程高手往往能用简单的方法解决复杂的问题。
除此之外,虽然我们都能认识到,代码要尽量写得简洁,要符合 KISS 原则,但怎样的代 码才算足够简洁?怎样的代码才算符合 KISS 原则呢?实际上,不是每个人都能准确地做出判 断,因此,在第 3 章介绍 KISS 原则的时候,我们会通过具体的代码示例详细说明。
6.可复用性(reusability)
我们可以将代码的可复用性简单地理解为“尽量减少重复代码的编写,复用已有代码”。 在后续章节中,我们会经常提到“可复用性”这一代码评价标准。例如,当介绍面向对象特性 的时候,我们会提到继承、多态存在的目的之一就是提高代码的可复用性;当介绍设计原则的 时候,我们会提到单一职责原则与代码的可复用性相关;当介绍重构技巧的时候,我们会提到 解耦、高内聚和模块化等能够提高代码的可复用性。可见,可复用性是一个重要的代码评价标 准,也是很多设计原则、设计思想和设计模式等所要实现的最终效果。
实际上,代码的可复用性与 DRY(Don’t Repeat Yourself)原则的关系紧密,因此,在第 3 章介绍 DRY 原则的时候,我们还会介绍代码复用相关的更多知识,如提高代码的可复用性的 编程方法等。
7.可测试性(testability)
相比上述 6 个代码质量评价标准,代码的可测试性较少被提及,但它同样重要。代码的可 测试性的高低可以从侧面准确地反映代码质量的高低。代码的可测试性低,难以编写单元测试,那么,基本能够说明代码的设计有问题。