Cognitive Complexity の Sequences of logical operators の考え方のメモ

# Cognitive Complexity とは

以下の記事が参考になったり、ググると色々と情報が見つかると思います。ここでは省略します。

* https://qiita.com/suzuki_sh/items/824c36b8d53dd2f1efcb

# Sequences of logical operators の扱い

以下のドキュメントが Cognitive Complexity の提案元です。

* https://www.sonarsource.com/docs/CognitiveComplexity.pdf

この中の Sequences of logical operators の考え方がいまいち理解できていません。

ドキュメントでは

> Cognitive Complexity does not increment for each binary logical operator. Instead, it assesses a fundamental increment for each sequence of binary logical operators.

や

> Because boolean expressions become more difficult to understand with mixed operators, Cognitive complexity increments for each new sequence of like operators.

と記載があり、また、以下のような例が示されています。

```c
if (a          // +1 for `if`
    && b && c  // +1
    || d || e  // +1
    && f)      // +1

if (a          // +1 for `if`
    &&         // +1
    !(b && c)) // +1
```

二つ目の例で、論理演算のところで Cognitive Complexity が +2 になるのが理解できていません。

Cognitive Complexity のツールでどうしているか、テスト用のソースコードを調べました。

## clang-tidy

* https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity.cpp

単に丸括弧で囲んだだけの場合は、丸括弧が無い場合と同様に扱っています。

論理演算の途中に関数呼び出しがあり、その引数に論理演算がある場合は、別扱いで加算しています。

前述の提案元のドキュメントにある `a && !(b && c)` の場合はテストに入っていないようです。

## Cognitive-Complexity-TS

* https://github.com/Deskbot/Cognitive-Complexity-TS/blob/master/test/cases/03-logical-operators.ts

丸括弧の有無について clang-tidy と同じようで、ただ、以下のテストが用意されている点が違います。

```ts
function parenthesesCanBreakSequence() {
    true && (true && true || true)  // → +3 としている
}
```

前述の提案元のドキュメントにある `a && !(b && c)` の場合はテストに入っていないようです。

## gocognit

* https://github.com/uudashr/gocognit/blob/master/testdata/src/a/a.go

前述の提案元のドキュメントにある `a && !(b && c)` がテストに入っていて +2 としています。

## phpcs-cognitive-complexity

* https://github.com/Rarst/phpcs-cognitive-complexity/blob/master/tests/Data/function10.php.inc

前述の提案元のドキュメントにある `a && !(b && c)` がテストに入っていて +2 としています。

## SonarQube C++ Community plugin (cxx plugin)

* https://github.com/SonarOpenCommunity/sonar-cxx/blob/master/cxx-squid/src/test/java/org/sonar/cxx/visitors/CxxCognitiveComplexityVisitorTest.java#L113
* https://github.com/SonarOpenCommunity/sonar-cxx/blob/master/cxx-squid/src/test/resources/visitors/binary_logical_not.cc

前述の提案元のドキュメントにある `a && !(b && c)` がテストに入っていて +2 としています。

# clang-tidy で試してみる

実際の動作も確認してみます。

```
ayweak@localhost:~/workspace/tmp$ clang-tidy-12 --version
LLVM (http://llvm.org/):
  LLVM version 12.0.0
```

以下のような内容のソースコードを対象にします。

```c:test3.c
#include <stdbool.h>

bool func1(bool a);
bool func2(bool a, bool b);

int main(int argc, char* argv[]) {
    true && true && true || true || true && true;

    true && !(true && true);

    true && func1(true && true) && true;
    true && func2(true && true, true && true) && true;

    true &&  true && true  && true;
    true && (true && true) && true;

    true ||  true && true  || true;
    true || (true && true) || true;

    true && (true && true || true);
    true && (true && true || true) && true;
}
```

compile_commands.json を用意しておきます。

```json:compile_commands.json
[
  { "directory": "/home/ayweak/workspace/tmp",
    "command": "/usr/bin/clang-12 -std=c11 -c -o test3.o test3.c",
    "file": "test3.c"
  }
]
```

Cognitive Complexity 以外のチェックを抑制、また、Cognitive Complexity の出力の閾値を下げるように設定ファイルを作成します。

```json:config.json
{
    "Checks": "-*,readability-function-cognitive-complexity",
    "CheckOptions": [
        { "key": "readability-function-cognitive-complexity.Threshold", "value": 1 }
    ]
}
```

以下のような結果になりました。

```
ayweak@localhost:~/workspace/tmp$ clang-tidy-12 --config-file=config.json test3.c
9 warnings generated.
test3.c:6:5: warning: function 'main' has cognitive complexity of 21 (threshold 1) [readability-function-cognitive-complexity]
int main(int argc, char* argv[]) {
    ^
test3.c:7:34: note: +1  → 提案元のドキュメントの例と同じ
    true && true && true || true || true && true;
                                 ^
test3.c:7:18: note: +1
    true && true && true || true || true && true;
                 ^
test3.c:7:42: note: +1
    true && true && true || true || true && true;
                                         ^
test3.c:9:10: note: +1  → 提案元のドキュメントの例と異なる
    true && !(true && true);
         ^
test3.c:11:33: note: +1
    true && func1(true && true) && true;
                                ^
test3.c:11:24: note: +1
    true && func1(true && true) && true;
                       ^
test3.c:12:47: note: +1
    true && func2(true && true, true && true) && true;
                                              ^
test3.c:12:24: note: +1
    true && func2(true && true, true && true) && true;
                       ^
test3.c:12:38: note: +1
    true && func2(true && true, true && true) && true;
                                     ^
test3.c:14:28: note: +1
    true &&  true && true  && true;
                           ^
test3.c:15:28: note: +1  → 丸括弧は影響しない
    true && (true && true) && true;
                           ^
test3.c:17:28: note: +1
    true ||  true && true  || true;
                           ^
test3.c:17:19: note: +1
    true ||  true && true  || true;
                  ^
test3.c:18:28: note: +1  → 丸括弧は影響しない
    true || (true && true) || true;
                           ^
test3.c:18:19: note: +1
    true || (true && true) || true;
                  ^
test3.c:20:10: note: +1  → Cognitive-Complexity-TS と同じく +3
    true && (true && true || true);
         ^
test3.c:20:27: note: +1
    true && (true && true || true);
                          ^
test3.c:20:19: note: +1
    true && (true && true || true);
                  ^
test3.c:21:36: note: +1
    true && (true && true || true) && true;
                                   ^
test3.c:21:27: note: +1
    true && (true && true || true) && true;
                          ^
test3.c:21:19: note: +1
    true && (true && true || true) && true;
                  ^
Suppressed 8 warnings (8 with check filters).
```