7

I prefer this writing style with early returns:

public static Type classify(int a, int b, int c) {
    if (!isTriangle(a, b, c)) {
        return Type.INVALID;
    }
    if (a == b && b == c) {
        return Type.EQUILATERAL;
    }
    if (b == c || a == b || c == a) {
        return Type.ISOSCELES;
    }
    return Type.SCALENE;
}

Unfortunately, every return statement increases the cyclomatic complexity metric calculated by Sonar. Consider this alternative:

public static Type classify(int a, int b, int c) {
    final Type result;
    if (!isTriangle(a, b, c)) {
        result = Type.INVALID;
    } else if (a == b && b == c) {
        result = Type.EQUILATERAL;
    } else if (b == c || a == b || c == a) {
        result = Type.ISOSCELES;
    } else {
        result = Type.SCALENE;
    }
    return result;
}

The cyclomatic complexity of this latter approach reported by Sonar is lower than the first, by 3. I have been told that this might be the result of a wrong implementation of the CC metrics. Or is Sonar correct, and this is really better? These related questions seem to disagree with that:

https://softwareengineering.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from

https://softwareengineering.stackexchange.com/questions/18454/should-i-return-from-a-function-early-or-use-an-if-statement

If I add support for a few more triangle types, the return statements will add up to make a significant difference in the metric and cause a Sonar violation. I don't want to stick a // NOSONAR on the method, as that might mask other problems by new features/bugs added to the method in the future. So I use the second version, even though I don't really like it. Is there a better way to handle the situation?

Community
  • 1
  • 1
janos
  • 120,954
  • 29
  • 226
  • 236
  • According to http://en.wikipedia.org/wiki/Cyclomatic_complexity, the CC is the number of linearly independent paths through the function, which is in both cases 4. Does Sonar tell you something different? – Doc Brown Sep 15 '14 at 08:23
  • 1
    Yeap. Sonar adds +1 for each `return` statement. It's the `squid:MethodCyclomaticComplexity` rule: https://dev.eclipse.org/sonar/rules/show/squid:MethodCyclomaticComplexity An earlier rule (but now deprecated in favor of squid) did not have this constraint: https://dev.eclipse.org/sonar/rules/show/checkstyle:com.puppycrawl.tools.checkstyle.checks.metrics.CyclomaticComplexityCheck – janos Sep 15 '14 at 08:30
  • so this question really means "how to prevent Sonar from calculating the CC wrong" - which is not a good fit for this site. Such questions are better placed on stackoverflow. I flag this question for migration. – Doc Brown Sep 15 '14 at 11:00
  • You're doing two things here. You have conditional logic but also a guard clause. I tend to respect the single responsibility concept, because in general it makes code more readable, except for guard clauses at the start of a function. – Pieter B Sep 15 '14 at 11:23
  • Earlier I asked a similar question (http://stackoverflow.com/questions/23381265/why-does-the-return-statement-increase-the-complexity) and it turned out that the complexity reported by SonarQube mixes the "Cyclomatic Complexity" and the "Essential Complexity". –  Sep 15 '14 at 11:56

3 Answers3

3

Your question relates to https://jira.codehaus.org/browse/SONAR-4857. For the time being all SonarQube analysers are mixing the cyclomatic complexity and essential complexity. From a theoretical point of view return statement should not increment the cc and this change is going to happen in the SQ ecosystem.

2

Not really an answer, but way too long for a comment.

This SONAR rule seems to be thoroughly broken. You could rewrite

b == c || a == b || c == a

as

b == c | a == b | c == a

and gain two points in this strange game (and maybe even some speed as branching is expensive; but this is on the discretion of the JITc, anyway).

The old rule claims, that the cyclomatic complexity is related to the number of tests. The new one doesn't, and that's a good thing as obviously the number of meaningfull tests for your both snippets is exactly the same.

Is there a better way to handle the situation?

Actually, I do have an answer: For each early return use | instead of || once. :D

Now seriously: There is a bug requesting annotations allowing to disable a single rule, which is marked as fixed. I din't look any further.

Community
  • 1
  • 1
maaartinus
  • 44,714
  • 32
  • 161
  • 320
1

Since the question is also about early return statements as a coding style, it would be helpful to consider the effect of size on the return style. If the method or function is small, less than say 30 lines, early returns are no problem, because anyone reading the code can see the whole method at a glance including all of the returns. In larger methods or functions, an early return can be a trap unintentionally set for the reader. If the early return occurs above the code the reader is looking at, and the reader doesn't know the return is above or forgets that it is above, the reader will misunderstand the code. Production code can be too big to fit on one screen.

So whoever is managing a code base for complexity should be allowing for method size in cases where the complexity appears to be problem. If the code takes more than one screen, a more pedantic return style may be justified. If the method or function is small, don't worry about it.

(I use Sonar and have experienced this same issue.)

  • 2
    I strongly disagree. You're right with the part that in a small method, early returns are fine. Actually, in a small method, about everything is fine. In a large method, everything is a problem, and that's the point. Why does anyone write methods over 30 lines??? Looking at the code I wrote yesterday half sleeping I can see some mess there and the mess are the big methods (about 30 lines). **Get rid of big methods**, and you'll be fine. **If the code takes more than one screen, refactor.** – maaartinus Nov 12 '14 at 17:35
  • I prefer single return statements, and follow it for myself as a rule. And, the only reason I have is that it reduces "code reading complexity". I can recall at least twice having problems with multiple return statements in my own code in about last 20 years. The only case which I have as an exception is an early return statement based based on invalid arguments to the function, if that is not handled using Exceptions. – Ozair Kafray Jul 29 '22 at 12:36