20

最近我们公司开始每周测量我们代码中函数的圈复杂度(CC),并报告哪些函数得到了改进或恶化。所以我们开始更加关注函数的CC。

我读过 CC 可以非正式地计算为 1 + 函数中的决策点数(例如 if 语句、for 循环、选择等),或者通过函数的路径数......

我知道减少CC的最简单方法是重复使用Extract Method重构......

有些事情我不确定,例如以下代码片段的 CC 是什么?

1)

for (int i = 0; i < 3; i++)
    Console.WriteLine("Hello");

Console.WriteLine("Hello");
Console.WriteLine("Hello");
Console.WriteLine("Hello");

他们都做同样的事情,但是第一个版本是否因为 for 语句而具有更高的 CC 呢?

2)

if (condition1)
    if (condition2)
        if (condition 3)
            Console.WriteLine("wibble");

if (condition1 && condition2 && condition3)
    Console.WriteLine("wibble");

假设该语言进行短路评估,例如 C#,那么这两个代码片段具有相同的效果......但是第一个片段的 CC 是否更高,因为它有 3 个决策点/if 语句?

3)

if (condition1)
{
    Console.WriteLine("one");

    if (condition2)
        Console.WriteLine("one and two");
}

if (condition3)
    Console.WriteLine("fizz");

if (condition4)
    Console.WriteLine("buzz");

这两个代码片段做了不同的事情,但是他们有相同的CC吗?还是第一个片段中的嵌套 if 语句有更高的 CC?即嵌套的 if 语句在心理上更难理解,但这反映在 CC 中吗?

4

8 回答 8

9
  1. 是的。您的第一个示例有一个决策点,而您的第二个没有,因此第一个示例具有更高的 CC。
  2. 是的-也许,您的第一个示例有多个决策点,因此 CC 更高。(请参阅下面的说明。)
  3. 也许吧。显然它们的决策点数量相同,但是计算 CC 的方法不同,这意味着......

...如果您的公司以特定方式测量 CC,那么您需要熟悉该方法(希望他们正在使用工具来执行此操作)。对于不同的情况(case 语句、布尔运算符等)有不同的计算 CC 的方法,但是无论您使用什么约定,您都应该从度量中获得相同类型的信息。

更大的问题是其他人提到的,您的公司似乎更关注 CC 而不是其背后的代码。总的来说,当然,5以下很好,10以下很好,20以下还可以,21到50应该是一个警告信号,50以上应该是一个大警告信号,但这些是指导,不是绝对的规则。您可能应该检查 CC 高于 50 的过程中的代码,以确保它不仅仅是一大堆代码,但可能有特定原因导致该过程以这种方式编写,而且它不可行(对于任何原因)来重构它。

如果您使用工具重构代码以减少 CC,请确保您了解这些工具在做什么,并且它们不是简单地将一个问题转移到另一个地方。最终,您希望您的代码几乎没有缺陷、能够正常工作并且相对容易维护。如果该代码的 CC 也较低,那就太好了。如果您的代码符合这些标准并且 CC 高于 10,那么也许是时候坐下来尽您所能为您的代码辩护(也许让他们检查他们的政策)。

于 2008-10-15T14:22:31.610 回答
6

通过维基百科条目和 Thomas J. McCabe 的 原始论文浏览后,您上面提到的项目似乎是该指标的已知问题。

但是,大多数指标确实有利有弊。我想在一个足够大的程序中,CC 值可能指向代码的可能复杂部分。但更高的CC并不一定意味着复杂。

于 2008-10-15T11:26:09.360 回答
5

像所有软件指标一样,CC 并不完美。在足够大的代码库上使用,它可以让您了解可能存在问题的区域。

这里有两点要记住:

  1. 足够大的代码库:在任何不平凡的项目中,您都会拥有具有非常高 CC 值的函数。如此之高,以至于在您的一个示例中,CC 是 2 还是 3 都没有关系。假设 CC 超过 300 的函数绝对值得分析。CC是301还是302都没关系。
  2. 别忘了用你的脑袋。有些方法需要很多决策点。通常可以以某种方式重构它们以减少它们,但有时它们不能。不要遵循“使用 CC > xy 重构所有方法”之类的规则。看看他们,用你的大脑来决定做什么。

我喜欢每周分析的想法。在质量控制中,趋势分析是在问题产生过程中识别问题的一种非常有效的工具。这比必须等到它们变得如此之大以至于它们变得明显要好得多(有关一些详细信息,请参阅SPC)。

于 2008-10-15T11:51:41.010 回答
4

CC 不是衡量质量的灵丹妙药。显然,重复语句并不比循环“更好”,即使循环具有更大的 CC。循环具有更大 CC 的原因是有时它可能会被执行,有时它可能不会执行,这导致两个不同的“案例”都应该被测试。在您的情况下,循环将始终执行三次,因为您使用了一个常量,但 CC 不够聪明,无法检测到这一点。

与示例 2 中的链式 if 相同 - 此结构允许您拥有一个语句,该语句仅在 condition1 和 condition2 为真时执行。这是一种特殊情况,在使用 && 的情况下是不可能的。因此,即使您在代码中不使用 if 链,它也有更大的潜力用于特殊情况。

于 2008-10-15T11:25:58.860 回答
1

这是盲目应用任何指标的危险。CC 指标当然有很多优点,但与任何其他改进代码的技术一样,它不能脱离上下文进行评估。将您的管理层指向 Casper Jone 对代码行测量的讨论(希望我能为您找到一个链接)。他指出,如果代码行数可以很好地衡量生产力,那么汇编语言开发人员就是地球上生产力最高的开发人员。当然,他们并不比其他开发人员更有效率;只需要他们更多的代码来完成高级语言用更少的源代码所做的事情。正如我所说,我提到了这一点,这样您就可以向您的经理展示盲目地应用指标是多么愚蠢,而无需对指标告诉您的内容进行智能审查。

我建议如果不是,您的管理层明智地使用 CC 度量来发现代码中应该进一步审查的潜在热点。盲目地以降低 CC 为目标而没有参考代码可维护性或其他良好编码的措施是愚蠢的。

于 2008-10-15T11:47:49.830 回答
1

圈复杂度类似于温度。它们都是度量,在大多数情况下,没有上下文就毫无意义。如果我说外面的温度是 72 度,那并没有多大意义;但如果我加上我在北极的事实,72 这个数字就变得很重要了。如果有人告诉我一个方法的圈复杂度为 10,那么如果没有它的上下文,我就无法确定它是好是坏。

当我对现有应用程序进行代码审查时,我发现圈复杂度是一个有用的“起点”指标。我首先检查的是 CC > 10 的方法。这些“>10”的方法不一定是坏的。它们只是为我提供了审查代码的起点。

考虑 CC 编号时的一般规则:

  • CC#和#of tests的关系,应该是CC# <= #tests
  • 只有在提高可维护性时才重构 CC#
  • CC 高于 10 通常表示一种或多种代码气味
于 2010-01-06T14:35:49.133 回答
0

[题外话] 如果您更喜欢指标的可读性而不是良好的分数(J.Spolsky 说过,“什么是衡量的,什么是完成的”?-这意味着指标被滥用的频率比我想象的要多),通常更好使用命名良好的布尔值来替换复杂的条件语句。

然后

if (condition1 && condition2 && condition3)
    Console.WriteLine("wibble");

变得

bool/boolean theWeatherIsFine =  condition1 && condition2 && condition3;

if (theWeatherIsFine)
    Console.WriteLine("wibble");
于 2008-10-15T11:38:24.547 回答
0

我不是这方面的专家,但我想我会给我的两分钱。也许这就是这一切的价值。

Cyclomatic Complexity 似乎只是一种特定的自动快捷方式,用于查找潜在(但不是绝对)有问题的代码片段。但真正要解决的问题不就是测试吗?代码需要多少个测试用例?如果 CC 更高,但测试用例数量相同且代码更干净,则不必担心 CC。

1.) 那里没有决策点。那里只有一条通过程序的路径,两个版本中的任何一个都只有一个可能的结果。第一个更简洁更好,圈复杂度该死。

1个测试用例

2.) 在这两种情况下,你要么写“wibble”,要么不写。

两个测试用例

3.) 第一个可能没有结果,“一”或“一”和“一和二”。3条路径。第二个可能不会导致任何结果,两者中的任何一个,或两者兼而有之。4条路径。

前3个测试用例,后4个测试用例

于 2013-10-31T03:55:17.283 回答