Hi all,
If you couldn’t make the scheduled time, you can still watch the webinar on YouTube:
As usual, here are the questions participants asked:
How Cognitive Complexity works
Q. Is Cognitive Complexity the same for all readers? It seems that language experience is an important factor
A. That’s a good point and a fair question. In fact the default threshold on the Cognitive Complexity rule for C, C++ & Objective-C is 25 because in our initial testing, setting it to 15 like all the other languages raised “too many” issues on the suite of projects we test against. So that indicates that developers in those languages have a higher tolerance for complexity in control flow. Still it doesn’t mean that they’re not working harder, just that they’re used to it - they’ve developed those mental ‘muscles’.
Similarly, I would expect that developers in all languages develop those muscles as they gain experience. But just because you can work hard doesn’t me you have to or that you should. Even for experienced developers, I think it’s valid to keep Cognitive Complexity as low as possible so you can save those mental cycles for other things (like solving the real problem).
Q. Local functions are penalized compared to “regular” functions yet they somehow give some extra info as they are supposed to be called only in a specific context. If we exclude the cases where variables/parameters are captured, they also don’t increase the mental load of the function. Is there any plan to make changes in this case?
A. I’m not sure I agree that they don’t increase the mental load of the function. In fact I would say keeping that specific context in mind is exactly what’s taken into account here.
Q. Are we certain we need to “ignore” statements that wrap multiple statements? I would say this is not the same as a method, because methods have names, the ternary operator (example) does not
A. The ternary operator is specifically not ignored, altho it does get a slight discount versus the equivalent if
/else
statement.
Q. What’s the main reason to consider simple ternary and if/else structure as equivalent in complexity?
Code 1:
if (cond) {
result = case1
} else {
result = case2
}
Code 2:
result = cond ? case1 : case2
A. Because they’re structurally/functionally equivalent. Ternary does get a slight discount (no increment for the else
) versus the full if
/else
but it is not ignored as “readable shorthand” because… it’s not.
Q. Having a chain of catch
blocks or a hierarchy of Exception
handling increases Cognitive complexity?
A. Yes it does, but not try
/finally
. From the white paper:
A catch
represents a kind of branch in the control flow just as much as an if
. Therefore, each catch
clause results in a structural increment to Cognitive Complexity. Note that a catch
only adds one point to the Cognitive Complexity score, no matter how many exception types are caught. try
and finally
blocks are ignored altogether.
You can read more in our white paper here: Cognitive Complexity | Sonar SonarSource
Q. Is the usage of ENUM
instead of primitive values considered in decreasing Cognitive Complexity?
A. Only inasmuch as it allows you to reduce the Cognitive Complexity of your control flow. For example, I was recently reminded that while you can switch on a String
in Java, you can’t do that in C++.
Q. Is Cognitive Complexity incremented for if
/else
within a lambda in Java?
A. Yes, and inside a lambda, there will be a nesting increment as well.
Q. I have trouble understanding the reasoning why else if
increases the counter, whereas a switch
’s case
doesn’t, and they’re a bit similar. else if
doesn’t break the linear flow, and we kind of already paid a branching cost. Just trying to understand the reasoning
A. This is a great question. First, an else if
is considered a (further) break in the linear flow. As to why a switch
is “cheaper”: I know that the switch
tests a single variable for equality against a series of hardcoded values. For an if
/else if
tree, I have no such guarantee. I must process each individual else if
(is it testing the same variable or something entirely different? Is it an equality test? Inequality? Less than/greater than? Is it testing other variables as well?) to understand what’s going on.
Q. In some languages, “if” is faster than “switch”. How is this balanced in SonarLint?
A. Cognitive Complexity is not about runtime speed but about Maintainer speed. I.e. it’s about how quickly and easily a programmer can understand the code.
Q. So Cognitive Complexity deals strictly with how PEOPLE understand the code, not how many operations the computer does?
A. Correct. It is a measure of how easily a piece of code can be understood by a human (probably a developer). Not how a machine interprets that code.
Q. What is the default Cognitive Complexity allowed?
A. For most languages the default threshold is 15. However, the default threshold is 25 for C, C++ and Objective-C because our initial testing indicated that developers in these languages have a higher tolerance. And it’s worth pointing out that rule thresholds are adjustable.
Refactoring with Cognitive Complexity
Q. Is extracting methods repeatedly until it doesn’t make any sense, a good refactoring step?
A. Well when put like that, obviously not. There is certainly a point of diminishing returns, and I think a good default stopping point is when the extracted method is below the threshold and no longer raises an issue.
Q. Can Single Responsibility Principle help in decreasing Cognitive Complexity?
A. Interesting question. SRP would not directly decrease Cognitive Complexity, but I can see how they would complement each other.
In some cases it might actually increase or decrease Cognitive Complexity scores based on how it was implemented.
Q. Would it make sense to “lower” every statement to its base form? I.e. instead of switch
, a sequence of if
/else
, instead of the ternary operator, an if
/else
. Remove “syntactic sugar” in other words.
A. While I’ll always argue against a ternary, in general I’m not in favor of dropping uses of syntactic sugar or of switches
(which I wouldn’t necessarily consider syntactic sugar). In fact, I’m in favor of using them as much as possible. The null-coalescing operator you see in some languages is a prime example of why. Once you understand the syntax, it’s far easier to read. Here’s the example in the whitepaper:
MyObj myObj = null;
if (a != null) {
myObj = a.myObj;
}
versus
MyObj myObj = a?.myObj;
They do the same thing, but the null-coalescing version is easier to read and faster to comprehend because it has fewer possible outcomes than the long form. Similarly, a switch
is easier than the corresponding if
tree because there are fewer possibilities (see my answer about switch
versus else if
above).
Q. We see here that one of the ways to reduce the Cognitive Complexity is early return
, but a clean OOP approach says having one return
from the method is a cleaner approach than having more return
s.
A. In fact, single-exit is a hold-over (hangover?) from much earlier languages and environments when you had a lot of cleanup to do before exiting a method. Multiple exits meant either junking up the code with multiple sets of cleanup or leaking resources. A semi-modern example is free
ing manually allocated memory in C. But with modern languages we don’t have to do that anymore. We can write readable code now.
Q. Does Sonar have any guidance for a complexity threshold beyond which it would be less effort to just rewrite the class rather than refactor? That Segment.java
seemed to me to have crossed the threshold.
A. If we could find a heuristic that accurately states “it’s just cheaper to just throw it away and start over”, we’d be a consulting firm
More seriously, we don’t consider Cognitive Complexity as a way to accurately measure this, especially when applying the Clean as you Code philosophy: Clean as You Code: How to win at Code Quality without even trying
Q. I’m wondering what is the recommendation for using Cognitive Complexity in SQ quality Gate? The metric is just a sum of complexity of the whole project, so each new piece of code usually adds to this value, which is not bad in itself. But it makes it impossible to define a useful threshold value for this metric. I don’t see a way to define it per function/class.
A. In fact, I would not recommend setting a Quality Gate condition on Cognitive Complexity. The metric is calculated and made available at the file level and above not for boolean decision making but as a tool for review.
It’s impossible to say for any given project how much is too much. For instance the total Cognitive Complexity of a calculator program will be (should be!) far, far less than for a car braking system. And if you try to squish the braking system complexity into the calculator program’s allowance, you’re absolutely going to break something.
Instead, I would keep the focus at the method level and making sure that each method has a reasonable complexity, which you can enforce using the Cognitive Complexity rule implemented for most languages SonarSource supports.
How to get it / tooling
Q. Is this metric included in all the Sonar products?
A. Raising issues on Cognitive Complexity can be found in SonarLint, SonarQube, and SonarCloud (so yes, all 3!). The live coding demonstrations were done in IntelliJ’s IDEA using SonarLInt to show the Cognitive Complexity issues
Q. What languages are supported for tracking Cognitive Complexity?
A. Apex, C, C++, C#, Flex, Go, Java, JavaScript, Kotlin, Objective-C, PHP, Python, Ruby, Scala, Swift, TypeScript, and VB.NET
Q. I’m using SonarLint in VisualStudio and I can’t find the secondary locations for my Cognitive Complexity issues in C#. What’s going on?
A. Unfortunately Visual Studio doesn’t expose the data we need to show secondary locations for live issues for C# and VB.NET (i.e. issues detected in the IDE). We can show secondary locations for C#/VB.NET for some types of issue where the issue data is fetched from the server in SonarLint’s Connected Mode (i.e. Taint Vulnerabilities and Security Hotspots).For other languages (C, C++, JavaScript, TypeScript) secondary locations are available for all issue types.
Q. Do I need a SonarQube/SonarCloud account/server to be linked with the SonarLint?
A. While Connected Mode is a great way to get maximum benefit from SonarLint, it is not required to raise issues when Cognitive Complexity is too high.
Q. Does SonarLint show a Cognitive Complexity analysis, even when there is no Sonar issue with the method? Like if the complexity were less than 15 on the method, could we see the complexity analysis, anyway?
A. Today, it’s not possible. Your feedback is welcome in https://community.sonarsource.com/c/suggestions/12!
Going further
Q. Also for DB2 DDLs
A. We’ve done some work on similar/equivalent calculations for SQL logic, but it has not yet come to fruition.
Q. What about variable/field naming similarity? If a method has variables with very similar names, it certainly becomes harder to understand!
A. We don’t consider this in Cognitive Complexity, but keep in mind that we do have many rules around Code Smells and Maintainability in SonarQube / SonarCloud / SonarLint
Q. Have you looked at whether some of the simplifications can be suggested automatically to the user? To help them refactor and reduce the complexity
A. This is a great question! In fact we don’t currently do much with auto-fixes or suggestions, but we’re looking at that.
Still have questions?
If you have more questions about Cognitive Complexity or general questions about SonarSource products or features, open a new thread here the community. We’ll be glad to see you!
For questions about commercial editions, including pricing or trial licenses, try one of these:
For other commercial questions, contact us here.
Ann