引入代码质量指标
在本节中,你将了解如何衡量软件的总体质量。我们将介绍 PHP 世界中最常用的一些指标,并解释它们能说明代码的哪些问题、如何收集这些指标以及它们何时有用、何时没用。
软件质量方面
在深入研究数字之前,我们首先需要澄清一件重要的事情:软件质量究竟意味着什么?当然,每个人对质量都有一定的理解,但要用语言表达出来可能很难。幸运的是,现在已经有了一些模型,比如惠普公司早在 20 世纪 80 年代就开发出的 FURPS 模型。其缩写为
-
功能性:软件是否能够处理各种使用情况?开发时是否考虑了安全性?
-
可用性:用户体验如何?是否有文档记录并易于理解?
-
可靠性:软件是否随时可用?可能影响输出的崩溃或错误的可能性有多大?
-
性能:表示软件的运行速度。它是否有效利用了可用资源?扩展性是否良好?
-
可支持性:软件能否得到很好的测试和维护?是否易于安装,能否翻译(本地化)成其它语言?
其它质量方面包括可访问性和法律一致性等。正如你所看到的,这个模型涵盖的用户体验和文档等方面比我们 PHP 开发人员通常要做的要多。因此,我们可以从两个不同的角度来看待软件质量:外部质量和内部质量。让我们仔细看看这意味着什么:
-
外部质量:外在或面向用户的方面是软件外部质量的一部分。这涵盖了我们之前介绍过的许多方面。它们的共同点是可以在不接触或分析代码本身的情况下对其进行测量,例如性能测试工具可以测量请求的响应时间,端到端测试可以通过在应用程序上自动执行测试来模拟用户。
-
内部质量:作为软件开发人员,我们通常更关心软件的内部质量。代码是否易读易懂?能否轻松扩展?能否编写测试?虽然用户永远不会看到代码,或者并不关心代码的可测试性,但这确实会间接影响到他们:高质量的代码包含较少的错误,而且通常(但并不总是)更快、更高效。众所周知,高质量代码也更容易扩展和维护。通常,这些方面都可以通过自动单元测试或代码分析器来检查。
在本书中,我们将重点放在内部代码质量上。这就是为什么我们特别谈论代码质量,而不使用更广泛的术语 "软件质量"。
代码质量指标
既然我们已经更好地理解了代码质量的含义,那么现在就让我们看看本节要讨论的代码质量指标:
-
代码行数
-
循环复杂度
-
NPath 复杂度
-
Halstead 指标
-
变更风险反模式指数
-
可维护性指数
代码行数
计算项目中的代码行数(LOC)并不是衡量项目质量的标准。不过,它是一个有用的工具,可以用来掌握项目的规模—例如,在开始工作时。此外,正如我们将要看到的,它还被其它指标用作计算的基础。例如,当你需要估算某些类的重构工作量时,了解你正在处理的代码行数也很有帮助。
这就是我们现在要仔细研究它的原因。首先,我们可以进一步区分 LOC:
-
LOC:LOC 简单地计算所有代码行,包括注释和空行。
-
代码注释行数(CLOC):该指标告诉你有多少行代码是注释。它可以作为源代码注释情况的指标。不过,正如我们所知,注释往往会腐烂(也就是说,它们很快就会过时,而且往往弊大于利),因此我们无法推荐任何百分比或其它经验法则。不过,了解一下还是很有意义的。
-
非注释代码行(NCLOC):如果你想比较一个项目和另一个项目的规模,撇开注释可以更好地了解你需要处理的实际代码数量。
-
逻辑代码行数(LLOC):对于这一指标,假设每条语句等于一行代码。下面的代码片段说明了它的工作原理。请看下面一行代码:
while($i < 5) { echo "test"; /* Increment by one */ $i++; }
在这里,LOC 将是 1。由于这一行有三条可执行语句,LLOC 将把这一行算作 3 行,因为每条语句也可以写在一行中:
while($i < 5) { echo "test"; /* Increment by one */ $i++; }
在上例中,我们突出显示了可执行语句。注释、空行和括号等语法元素都不是可执行语句—这就是为什么全行注释和循环末尾的收尾括号不计入逻辑行的原因。
循环复杂度
除了计算代码行数,我们还可以衡量代码的复杂性,例如,计算函数内执行路径的数量。一个常用的衡量标准是循环复杂度(CC)。该指标于 20 世纪 70 年代末引入,但现在仍然有用。这个令人费解的名字背后的想法很简单:我们计算决策点的数量,也就是 if
、while
、for
和 case
语句的数量。此外,函数入口也算作一条语句。
下面的示例说明了该指标的工作原理:
// first decision point
function someExample($a, $b)
{
// second decision point
if ($a < $b) {
echo "1";
} else {
echo "2";
}
// third decision point
if ($a > $b) {
echo "3";
} else {
echo "4";
}
}
前面代码段的 CC 应该是 3:函数入口算作第一条决策路径,两条 if
语句也各算作一条决策路径。不过,根据定义,else
语句不在考虑之列,因为它们是 if
子句的一部分。该指标尤其适用于快速评估你尚未了解的代码的复杂性。它通常用于检查单个函数,但也可用于类甚至整个应用程序。如果你有一个 CC
值很高的函数,可以考虑将其拆分成几个较小的函数来降低该值。
NPath 复杂度
代码复杂性的第二个指标是 NPath 复杂性。基本思想与 CC 类似,因为它也计算函数的决策路径。 但是,它计算所有可能的决策路径,而不仅仅是为 CC 定义的四个语句(if
、while
、for
和 case
)。此外,函数入口点不计为该度量的决策路径。
看上面的例子,NPath 复杂度是 4,因为我们有 2 * 2 条可能的函数路径:两个 if
语句,以及两个 else
语句。 因此,所有四个 echo 语句都被视为决策路径。 如前所述,不考虑函数调用本身。 现在,如果我们添加另一个 if 语句,NPath 复杂度将增加到 8。这是因为我们将有 2 * 2 * 2 条可能的路径。 换句话说,该指标呈指数增长,因此它可以迅速变得相当高。
NPath 复杂度描述了比 CC 更好地测试函数的实际工作量,因为它直接告诉我们需要测试函数的多少种可能结果才能实现 100% 的测试覆盖率。
霍尔斯特德指标
莫里斯-霍尔斯特德(Maurice Halstead)在 20 世纪 70 年代末提出了一套八项度量标准,至今仍在使用,被称为 "霍尔斯特德度量标准"。它们仅基于运算符(如 ==
、!=
和 &&
)和操作数(如函数名、变量和常量)的不同数量和总数,但正如你将看到的,它们已经告诉了你关于被检查代码的很多信息。
我们不需要确切了解这些指标的工作原理。如果你有兴趣,可以访问 https://www.verifysoft.com/en_halstead_metrics.html 了解更多关于这些指标的信息。不过,你应该对 Halstead 指标有所了解:
-
长度:计算运算符和操作数的总数之和就能知道我们必须处理多少代码
-
词汇量:运算符和操作数的总和已经说明了代码的复杂程度
-
容量:根据长度和词汇量来描述代码的信息含量
-
难度:表示容易出错的程度(即引入错误的可能性)
-
级别:反转难度,即级别越高,越不容易出错
-
努力程度:理解代码所需的努力
-
时间:告诉我们实现代码大概需要多长时间
-
错误:估计代码中包含的错误数量
通过这些值,你可以大致了解所处理的代码类型。是否易于理解?开发花费了多少时间?预计会出现多少错误?但是,如果不将这些值与其它应用程序的结果进行比较,它们对你的帮助就不会太大。
变更风险反模式指数
另一个特别有用的指标是变更风险反模式(CRAP)指数。它使用的是所考虑代码的 CC 和代码覆盖率。
这两个指标的结合非常有用。不过分复杂且测试覆盖率高的代码,要比复杂且测试不多的代码更有可能不出现错误并易于维护。
可维护性指数
本节的最后一个指标是可维护性指数。它只提供一个值来表示被检查代码的可维护性,或者换句话说,它告诉你在不引入新错误的情况下修改代码的难易程度。有两点让我们对这个指标特别感兴趣。
首先,它以上述指标为基础,使用 LOC、Halstead 指标和 CC 来计算指数。不过,我们并不需要知道具体的计算公式。如果你有兴趣,可以在这里查找: https://www.verifysoft.com/en_maintainability.html 。
其次,该指标将返回一个值,你可以用它来直接评估代码质量:
-
85 及以上:可维护性良好
-
65 至 85:中等可维护性
-
65 及以下:可维护性差
有了这个指标,你就不需要与其它代码进行比较了。因此,它对快速评估代码质量特别有用。
在本节中,我们已经学习了很多理论知识。到目前为止,我们的工作非常出色—你一定不会后悔学习了这些知识,因为在下一节中,我们将向你展示如何使用更多的 PHP 工具来收集这些指标。