为什么这个函数的圈复杂度是12?

Why is the cylcomatic complexity of this function 12?

我有一个 (C#) 函数可以检查四组条件并且 return 是一个布尔值。如果其中任何一个为真,则 returns true。我确定我可以简化逻辑,但我希望它具有可读性。

Visual Studios 中的 CodeMaid 扩展告诉我函数的循环复杂度是 12。我 looked it up 并且循环复杂度是

the number of independent paths through the source code

我不明白为什么它是 12。我可以用两种方式来思考它,或者圈复杂度应该是 2,因为它总是经过相同的路径但是可以 return 或者 truefalse。或者如果是 16 就可以理解,因为最后的四个布尔值 or'd 在一起可能是 true 或 false,2*2*2*2 = 16.

有人能告诉我为什么是 12 吗?甚至可以显示图表,以便我可以想象不同的路径?

public bool FitsCheckBoxCriteria(TaskClass tasks)
{
    // note: bool == true/false comparisons mean you don't have to cast 'bool?' as bool


    // if neither checkboxes are checked, show everything
    bool showEverything = NoShutDownRequiredCheckBox.IsChecked == false &&
                          ActiveRequiredCheckBox.IsChecked == false; 

    // if both are checked, only show active non-shutdown tasks
    bool showActiveNonShutdown = ActiveRequiredCheckBox.IsChecked == true &&
                                 tasks.Active == "YES" &&
                                 NoShutDownRequiredCheckBox.IsChecked == true &&
                                 tasks.ShutdownRequired == "NO";

    // if active is checked but shudown isn't, display all active
    bool showActive = ActiveRequiredCheckBox.IsChecked == true &&
                      tasks.Active == "YES" &&
                      NoShutDownRequiredCheckBox.IsChecked == false;

    // if non-shutdown is checked but active isn't, display all non-shutdown tasks
    bool showNonShutdown = NoShutDownRequiredCheckBox.IsChecked == true &&
                           tasks.ShutdownRequired == "NO" &&
                           ActiveRequiredCheckBox.IsChecked == false;

    return showEverything || showActiveNonShutdown || showActive || showNonShutdown;
}

提前致谢。

编辑:

我改成了这个。为复选框条件分配局部变量没有任何效果,但是从 "YES"/"NO" 中创建布尔值将复杂度提高到 14,我想我明白了。

public bool FitsCheckBoxCriteria(LubeTask tasks)
{
    bool noShutdownReqChecked = (bool)NoShutDownRequiredCheckBox.IsChecked;
    bool activeChecked = (bool)ActiveRequiredCheckBox.IsChecked;

    bool active = tasks.Active == "YES" ? true : false;
    bool shutdownReq = tasks.ShutdownRequired == "YES" ? true : false;

    // if neither checkboxes are checked, show everything
    bool showEverything = !noShutdownReqChecked && !activeChecked;

    // if both are checked, only show activeChecked non-shutdown tasks
    bool showActiveNonShutdown = activeChecked && noShutdownReqChecked && active && !shutdownReq;

    // if activeChecked is checked but shudown isn't, display all activeChecked
    bool showActive = activeChecked && !noShutdownReqChecked && active;

    // if non-shutdown is chceked but activeChecked isn't, display all non-shutdown tasks
    bool showNonShutdown = noShutdownReqChecked && !activeChecked && !shutdownReq;

    return showEverything || showActiveNonShutdown || showActive || showNonShutdown;
}

这只是一个猜测,但我认为每个赋值都是 +2 (if =true/else =false),然后对 return 语句中每个可能的退出条件 +1。所以它可能会变成这样的东西:

bool showEverything = false;
if (...) { showEverything = true; } +1
else { showEverything = false; } +1

bool showActiveNonShutdown = ... +2 if/else
bool showActive = ... +2 if/else
bool showNonShutdown = ... +2 if/else

if (showEverything) {...} +1
else if (showActiveNonShutdown) {...} +1
else if (showActive) {...} +1
else if (showNonShutdown) {...} +1
else {false}

C# 使用短路计算,这意味着如果存在 x && y 表达式,则仅在必要时才对 y 进行计算,更准确地说,如果 x 为真。这意味着 result = x && y; 有两个独立的执行路径:(1)如果 x 为假,则仅评估 xresult 获得假值(不评估 y) 但是 (2) 如果 x 为真 y 也被评估并且 result 得到 y 的评估结果。这意味着每个 &&|| 运算符都会增加圈复杂度,在第一个示例中有 8 个 && 和 3 个 ||运算符,因此该方法的圈复杂度为 12.

密钥在"independent paths".

我将重写您的代码以缩短它,以便我们进行讨论。

public bool FitsCheckBoxCriteria(TaskClass tasks)
{
    bool E1 = A1 && A2; 

    bool E2 = B1 && B2 && B3 && B4;

    bool E3 = C1 && C2 && C3;

    bool E4 = D1 && D2 && D3;

    return E1 || E2 || E3 || E4;
}

圈复杂度是独立路径的数量。这不是 return 值 (2) 的可能总数。

&& 运算符和 ||运营商正在短路操作;如果 A1 为假,A2 不被评估。同样,如果 E1 为真,则不评估 E2。

如果您将所有 && 替换为 &,将所有 || 替换为 |在上面的代码中,圈复杂度为 1,因为代码中只有一条路径。 (虽然这不会使它成为更好的代码)。

事实上,有 72 条可能的路径...

  1. A1,B1,C1,D1,E1求值;其他的不是。
  2. A1,A2,B1,C1,D1,E1被求值;其他的不是。
  3. A1、B1、B2、C1、D1、E1被求值;其他的不是。
  4. A1、A2、B1、B2、C1、D1、E1被求值;其他人不是。 等...

但是路径 4 不包含之前路径中没有的任何新代码。这是 "independent paths" 的定义 - 每个路径都必须包含新代码。

所以在这个例子中,你可以手数代码如下:

1 + 这段代码中短路运算符的个数(11) = 12.

Wikipedia has an excellent in depth explanation.