Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on Why static code analyzers such as SonarQube indicate a high code complexity for switch statements?

Parent

Why static code analyzers such as SonarQube indicate a high code complexity for switch statements?

+3
−0

During a presentation of a pipeline configuration, a colleague showed a SonarQube integration and one of its reports. A warning was caused by overrunning the max value for the code complexity threshold in a function containing a fairly large switch statement.

Why are switch statements considered too complex and what to do about them?

The problematic code was similar to the one below, but containing about double of the case statements:

enum FooStasus
{
    ToBeStarted = 1,
    Starting = 2,
    OnGoing = 3,
    OnHold = 4,
    Stopped = 5
}

private static double GetWeight(FooStasus status)
{
    switch (status)
    {
        case FooStasus.ToBeStarted:
        case FooStasus.Starting: return 1.0;
        case FooStasus.OnGoing: return 2.0;
        case FooStasus.OnHold: return 0.2;
        case FooStasus.Stopped: return 0;
        default: return 0.5;
    }
}

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

Cyclomatic complexity = the number of execution paths (2 comments)
Post
+2
−0

I would skip the theoretical part of actually computing the cyclomatic complexity of a switch statement and mention that it can see as a bunch of if statements. Since each if adds to the complexity, the higher the number of cases, the higher the complexity.

While a simple switch such as the one in the question is simple to read and maintain, if it grows way larger, it becomes easier to introduce bugs, even if C# prevents the flow control from passing from one case to another if the case has at least one instruction (i.e. each nonempty case must use break or return).

One way to mitigate this relies on the actual code complexity. If this is a simple mapping, a simple Dictionary can be used. Example:

private static Dictionary<FooStasus, double> _statusMap = new()
{
    { FooStasus.ToBeStarted, 1.0 },
    { FooStasus.Starting, 1.0 },
    { FooStasus.OnGoing, 2.0 },
    { FooStasus.OnHold, 0.1 },
    { FooStasus.Stopped, 0 },
};

private static double GetWeightSimple(FooStasus status) =>
    _statusMap.GetValueOrDefault(status, 0.5);

If the logic is more complex, the dictionary can hold Actions or Funcs that might rely on the case value to perform some logic. If they are more than in the following example, it would be wise to define them as separate functions:

private static Dictionary<FooStasus, Func<FooStasus, double>> GetWeightActMap = new()
{
    { FooStasus.ToBeStarted, _ => { Console.WriteLine("To be started"); return 1.0;} },
    { FooStasus.Starting, status => { Console.WriteLine($"Status is {status}"); return 1.0; } },
    { FooStasus.OnGoing, _ => 2.0 },
    { FooStasus.OnHold, _ => 0.1 },
    { FooStasus.Stopped, _ => default },
};

private static double GetWeightAct(FooStasus status) =>
    GetWeightActMap.GetValueOrDefault(status)?.Invoke(status) ?? default;

However, as suggested by elgonzo this should be avoided for anything that is a little more complex than my trivial example and use (local) functions instead, since they provide meaning to each case.

Also, whenever the mapping list is fairly long, a table might be more suitable for storage.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

1 comment thread

Nitpicker Central... :-) (4 comments)
Nitpicker Central... :-)
elgonzo‭ wrote about 2 years ago · edited about 2 years ago

(i.e. each nonempty case must use break or return)

... or a goto with to a case label or the default label ...


Out of curiosity, i wonder what SonarQube would say about switch expressions.

Because, using a dictionary just moves the complexity from a switch compound statement into the dictionary, thus turning code complexity into ..um.. data initialization complexity (???). In my opinion that's not a real benefit/improvement in itself given the availability of switch expressions since C# 8 (and the mentioned Func/Action dictionary is an outright degradation of code quaility -- holy moly, that's just a terrible hack...). In my cheap opinion it's all just a clever way to fool SonarQube's analytic capabilities without actually improving the code in any meaningful way...

elgonzo‭ wrote about 2 years ago · edited about 2 years ago

Generally, if a switch compound statement only serves to translate/map values, replace it with a switch expression.

If the case branches feature side-effects (some other code besides returning a value), either keep it a switch compound statement or refactor it into a switch expression with the case branches with side effects moved into (local) functions. This is a judgement call involving considerations of code readability/maintainability; and i am not sure whether there would be a definitive "one size fits all" answer to which would be preferable. (Personally, i tend to gravitate toward switch expressions with local functions for all but the most trivial cases, since well-chosen names for the local functions can be a huge benefit to code readability...)

Alexei‭ wrote about 2 years ago · edited about 2 years ago

elgonzo‭ Not familiar with how SonarQube actually works, but I guess it computes the cyclomatic complexity and complains about going beyond a certain threshold. So it ignores the actual instructions.

Ref. how to avoid I have never used anything beyond the simple mapping. And also in this case, I would use a database table in most cases.

And yes, splitting in various functions should be the best way (what you have mentioned).

elgonzo‭ wrote about 2 years ago · edited about 2 years ago

would use a database table in most cases.

Yeah. In my comments, i was focusing only on the question which was about switch-case compound statements used as value mapping/translation as part of C# code.

Of course, if value translation/mapping needs to be configurable or extensible, or otherwise be maintained outside of the code writing/maintenance process, it makes sense to not hard-code this (neither switch statements nor switch expressions will be really practical for this outside of perhaps some auxiliary and superficial role in flow control), but use some form of lookup that is either relying on or initialized by a data source outside of the program code.