13

我正在使用 php undercontrol 并且代码浏览器在每个 setter/getter 上报告一些 CRAP 索引错误,即这样的代码

public function getFoo()
{
    return $this->_foo;
}

单元测试涵盖了 getter/setter,因为没有 if/for/switch/foreach,所以没有复杂性。那么为什么我的代码的 CRAP 索引为 1 ???

PS:自我回答自己可能是因为复杂性没有,但我的主要问题是每个 getter/setter 都会因为 CRAP 索引而生成警告,所以无论如何要告诉 phpunit/php 代码覆盖率以使函数的 CRAP 等于 0复杂度指数为 0。

4

3 回答 3

30

最低 CRAP 分数是 1,而不是 0。这是因为 CRAP 的算法是

CRAP(m) = comp(m)^2 * (1 – cov(m)/100)^3 + comp(m)

并且函数的最小圈复杂度 (comp) 值为 1。所以问题不在于 phpunit,而是任何将 CRAP 标记为 1 的问题。

一般来说,您希望将您的 CRAP 阈值设置为 5 左右,任何更低的地方,您也可以只使用一个简单的代码覆盖率指标(并争取 100%),因为复杂性因素几乎没有影响。CRAP >= 30 意味着没有多少测试可以使您的方法不蹩脚。

圈复杂度通常(但有多个定义)可以手动计算为:

  • 函数调用加 1 分
  • 每循环加1分
  • 每个分支加 1 分
于 2011-10-06T08:22:59.777 回答
1

真的是警告吗?通常,警告的阈值设置为远高于 1(可能约为 30)。这里有一篇很好的 SO 帖子,显示了如何计算数字。我的 phpunit 设置中似乎有一些硬编码值,CRAP 为 30。

根据CRAP 指数的创建者Alberto Savoia的说法:

“CRAP(变更风险分析和预测)指数旨在分析和预测维护现有代码体所需的工作量、痛苦和时间。”

最小 CRAP 数将是覆盖率为 100% 的代码的圈复杂度。这个想法是对复杂代码的更改比对简单代码的更改更有可能产生问题。

于 2011-10-06T06:33:58.990 回答
1

CRAP只是一个指标。就其本身而言,它与“一根绳子有多长?”一样有用。除了是一个不确定答案的问题之外,它也是一个不确定问题的答案。

如果您知道它测量的是什么,那么您可以将其用作复杂性的一个非常基本的指标。要更多地使用它,您需要相当多的经验来比较实现。之后,您理想地希望在同一事物的实现之间进行比较。之后,您需要深入了解正在测试的代码,如果这样做,您可能比 CRAP 分数有更好的洞察力。

它越高,在可测试性(包括效率)和变化点等几个方面改进它的可能性就越高。然而,直到分数超过 8000 或 9000 时,某事绝对是 CRAP 的概率才开始接近确定性。像处理解析的 XML 文档中的节点以实现无法以任何决定性方式改进的功能这样基本的事情可以轻松地将复杂性提高到数百个,同时又非常好。

这有点像花钱。出于特定目的,您可能必须花费最低金额。它可能是一百万,也可能是一千,但无论出于何种目的,我们都倾向于假设支出越高,它的可能性就越大。但也许它需要很高,也许你正在购买一艘游艇。天真地强迫数字下降不仅会在另一个方向上犯同样的错误,而且真的很危险。由于这种灾难性的思维错误,71 人在 Grenfell Tower 被烧死或窒息,他们认为仅凭数字就能做到最好。

您不应该假设减少 CRAP 会提高可测试性或可维护性。很多时候,高 CRAP 只是报告了强制性复杂性的衡量标准。您可以在技术上减少 CRAP,玩弄数字,同时降低可测试性、可维护性和可读性。你只能通过实际改进它们来改进这些东西。CRAP 甚至不是一个可靠的改进措施。有时 CRAP 在改善后可能会下降。有时它可能会上涨。问题在于度量游戏是人们经常只是取代问题或隐藏被衡量的事物作为复杂性的指标。

一个常见的例子是使用映射而不是 switch 或 if 语句。我倾向于虔诚地自己做这件事。然而,我们忘记了我们正在取代复杂性。在这种情况下,我们可以,我们有一个带有地图实用程序的库,该实用程序得到维护并且可以依赖。如果您将该 map 函数与一些 if 语句相比,那么复杂性的总体衡量标准将是巨大的。当您没有这样的实用程序可供您使用时,您需要非常小心如何降低复杂性。例如,如果您尝试完全消除 if 语句和 for 循环,我祝您好运。

如果可以提高测试速度,圈复杂度确实会很好地反映。这适用于诸如函数中有两个 if 语句的情况。如果第二个依赖的状态根据第一个是否匹配而有所不同,那么您必须冗余运行第一个(4 次而不是 2 次)。当您经常组合代码时,可能的排列会非线性上升。如果您有八个函数接受一个布尔值并返回一个布尔值,那么您可以单独测试每个函数以获得 8 次 2(布尔值有两个可能的输入值)测试(16 个测试)。但是,如果您结合所有这些功能,则有 256 种不同的可能输入组合。圈复杂度可以帮助指出可能的情况。

它还给出了一些关于函数时间参数方面您真正需要多少测试的指示。如果您有一个带有一个布尔参数和一个基于它的 if 语句的函数,那么您至少需要两个测试才能获得至少完整的代码覆盖率。两个布尔参数和两个 if 语句,然后是四个或三个,具体取决于 if 是否嵌套。在最坏的情况下,如果在 if 之后添加,您可能需要测试的组合数量仅增加 2 的幂。

这可能会产生冲突,迫使您过早地对代码进行分段,因为在运行时您可能永远不会真正遇到这个问题。在测试开始消耗大量资源或实际上开始变得异常笨拙之前,您通常不应该担心这一点。在这种情况下,您不会依赖 CRAP,而是了解代码的执行和基准。

CRAP 可能会弄错,因为它对复杂性做出了相当幼稚的猜测。你正在接近一个悲观或最坏情况的估计。我正在查看一段具有高 CRAP 的代码,但它无法区分具有if($constantInScope)etc;etc;if($constantInScope)etc;if($varA)etc;etc;if($varB)etc;.

如果单个函数在执行路径方面确实有数百、数千或数百万个可能的结果,那么这可能是一个很好的迹象,表明测试它存在问题。这可能是不可避免的。相反,测试可能比指示的要容易得多。圈复杂度的测量能力有限,这可能就是为什么 CRAP 似乎也包含了覆盖权重的原因。如果您对其进行了测试并获得了很多覆盖率,那么它可能并不像圈复杂度所想的那样难以测试,特别是请记住,有可能使事情无法仅根据执行路径进行全面测试,尽管很稀少。

一个简单的例子说明为什么 CRAP 没用,展开循环并用数学语句等替换 if 语句以减少 CRAP。

于 2019-06-26T18:29:46.620 回答