SFML: Consider clang-tidy `readability-function-cognitive-complexity` check
Subject of the issue
clang-tidy readability-function-cognitive-complexity docs:
https://releases.llvm.org/14.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.html
Tentative implementation of this check: https://github.com/SFML/SFML/compare/master...ChrisThrasher:SFML:cognitive-complexity
Talk by Sonar employee explaining the utility of this complexity metric: https://www.youtube.com/watch?v=el9OKGrqU6o
clang-tidy has a check that implement this paper from SonarSource which defines a concept of “cognitive complexity”. The idea is that it is an empirical measures of how hard it is to understand all possible code paths through a function. What I love about this check is that it forces us to reckon with functions that have exceptionally complicated control flow and break it apart into multiple functions that are hopefully easier to read.
The default cognitive complexity score that clang-tidy uses is 25 but we have functions in SFML that score 110. It’s safe to say that SFML won’t get under a score of 25 but I think it’s possible we can lower that threshold down to somewhere around 75 and in doing so will improve some of the most complicated bits of code we currently maintain. Not only will we improve existing code but we will ensure that no new code is checked into that repo that violates this quality gate. I expect that this process will be very incremental as we lower the cognitive complexity threshold from 110 by increments of 5 or so points.
We can potentially merge this new check in its current state which requires no changes to the code. This will set a new quality floor (albeit a very low floor) such that no new functions exceed this threshold. It’d be a minor step but still potentially worthwhile.
Here’s a simpler example of readability-function-cognitive-complexity’s output from a different project:
/Users/thrasher/Projects/fourier-draw/src/main.cpp:27:5: error: function 'main' has cognitive complexity of 35 (threshold 25) [readability-function-cognitive-complexity,-warnings-as-errors]
int main()
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:37:5: note: +1, including nesting penalty of 0, nesting level increased to 1
if (!font.loadFromFile(FONT_PATH / std::filesystem::path("font.ttf")))
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:49:5: note: +1, including nesting penalty of 0, nesting level increased to 1
while (window.isOpen()) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:50:9: note: +2, including nesting penalty of 1, nesting level increased to 2
for (auto event = sf::Event(); window.pollEvent(event);) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:51:13: note: +3, including nesting penalty of 2, nesting level increased to 3
switch (event.type) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:56:17: note: +4, including nesting penalty of 3, nesting level increased to 4
switch (event.key.code) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:75:9: note: +2, including nesting penalty of 1, nesting level increased to 2
if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:77:51: note: +1
const auto is_within_x = mouse.x >= 0 && mouse.x <= int(window.getSize().x);
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:78:51: note: +1
const auto is_within_y = mouse.y >= 0 && mouse.y <= int(window.getSize().y);
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:79:13: note: +3, including nesting penalty of 2, nesting level increased to 3
if (is_within_x && is_within_y) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:79:29: note: +1
if (is_within_x && is_within_y) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:83:23: note: nesting level increased to 4
= [position, &signal, &line, &line_shadow, &x_epicycles, &y_epicycles, &frame_count]() {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:90:17: note: +4, including nesting penalty of 3, nesting level increased to 4
if (signal.empty()) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:92:19: note: +1, nesting level increased to 4
} else {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:94:21: note: +5, including nesting penalty of 4, nesting level increased to 5
if (vector.length() > 2)
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:100:9: note: +2, including nesting penalty of 1, nesting level increased to 2
if (x_epicycles.empty()) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:109:9: note: +2, including nesting penalty of 1, nesting level increased to 2
if (frame_count % 2 == 0) {
^
/Users/thrasher/Projects/fourier-draw/src/main.cpp:131:9: note: +2, including nesting penalty of 1, nesting level increased to 2
if (++frame_count == 2 * x_epicycles.size()) {
^
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 16 (16 by maintainers)
I want to emphasize this an objective complexity metric. I’m sure you’re aware but it bears repeating. I agree complexity in general is highly subjective though. Frustratingly subjective sometimes.
This is a possibility. It’s plausible we have to make exceptions for select functions. As with a few other clang-tidy checks, 100% compliance is sometimes impractical.
When I read through the biggest functions that this check found (scores >=80), my impression was “I sure hope I never have to touch this”. Fixing bugs or enhancing features in these functions is liable to be a huge burden when they contain so much state and so much control flow. I doubt most developers could easily grok them without spending significant time and effort getting to understand the code.
An immediate problem? No. A future problem? Potentially.
I’ll admit I have relatively little experience with writing code that adheres to these complexity metric so I may sour on this idea after enough experience. I do however think it’s a worthwhile experiment. I’m personally experimenting in some personal projects to get a better idea for what it entails in practice and to get better at breaking apart big functions.