Webinar: Refactoring with Cognitive Complexity

Hi all,

I’m hosting a webinar on the 30th to talk about Cognitive Complexity. I’d love to see you there!
 

What: Cognitive Complexity - an intro and discussion of how to use it as a guide to refactoring for more readable code

When: June 30 10:00 a.m. CDT / 17:00 CEST

Presenter: G. Ann Campbell (me!)

We first published Cognitive Complexity in December 2016, and in the years since it has become an increasingly important metric in daily development work as more and more coders have adopted it.

Join me on Wednesday, June 30th, join me to discuss Cognitive Complexity and how you can use it to refactor your code for better understandability.

Intended audience: Anyone interested in learning more about this topic

Register now to reserve your seat.

 
:grinning_face_with_smiling_eyes:
Ann

P.S. Can’t make it? Register anyway and we’ll send you the recording!

P.P.S. After the webinar we’ll post the Q&A in this thread like we usually do.

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 :slight_smile:
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. :slight_smile:

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 returns.
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 freeing manually allocated memory in C. But with modern languages we don’t have to do that anymore. We can write readable code now. :slight_smile:

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 :wink:
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.

 
:grinning:
Ann

4 Likes

As a followup, I want to share the resource links I promised during the webinar. It seems not all of them were included in the email like I promised. :slightly_frowning_face:

And of course:

 
:smiley:
Ann

3 Likes

Informative and well presented, thanks!

2 Likes

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.

I would argue for the case mentioned here. IMO the code 2 is simpler in the sense that I don’t need to read the content of the if and the else to understand that the same variable is assigned in the two paths. When there is an if/else the 2 blocks could assign different variables or assign only in 1 branch whereas the ternary gives the direct insight that you simply assign different values based on a condition.

1 Like

Hi @AmauryL,

Welcome to the community!

Ehm… I think you still do need to read it. You’ve presented a simplified, almost pseudo-code example. How about:

result = a && b? c++ : ++c;

or even

result = a && b? c=4: d=a*b;

My point is that a ternary is a full-featured if/else in an unpleasantly compact (& IMO hard to read) package.

 
Ann

1 Like