Cognitive Complexity とは
以下の記事が参考になったり、ググると色々と情報が見つかると思います。ここでは省略します。
Sequences of logical operators の扱い
以下のドキュメントが Cognitive Complexity の提案元です。
この中の 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.
と記載があり、また、以下のような例が示されています。
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
単に丸括弧で囲んだだけの場合は、丸括弧が無い場合と同様に扱っています。
論理演算の途中に関数呼び出しがあり、その引数に論理演算がある場合は、別扱いで加算しています。
前述の提案元のドキュメントにある a && !(b && c)
の場合はテストに入っていないようです。
Cognitive-Complexity-TS
丸括弧の有無について clang-tidy と同じようで、ただ、以下のテストが用意されている点が違います。
function parenthesesCanBreakSequence() {
true && (true && true || true) // → +3 としている
}
前述の提案元のドキュメントにある a && !(b && c)
の場合はテストに入っていないようです。
gocognit
前述の提案元のドキュメントにある a && !(b && c)
がテストに入っていて +2 としています。
phpcs-cognitive-complexity
前述の提案元のドキュメントにある 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
以下のような内容のソースコードを対象にします。
# 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 を用意しておきます。
[
{ "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 の出力の閾値を下げるように設定ファイルを作成します。
{
"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).