and this one https://marketplace.visualstudio.com/items?itemName=Stepsize...
These two haven't tried before but doing it now:
https://marketplace.visualstudio.com/items?itemName=selcuk-u...
https://marketplace.visualstudio.com/items?itemName=TomiTurt...
switch (foo) {
case KIND_1: return parseObjectOfKind1();
case KIND_2: return parseObjectOfKind2();
case KIND_3: return parseObjectOfKind3();
...
case KIND_15: return parseObjectOfKind5();
case KIND_16: return parseObjectOfKind16();
}
There are 16 paths and yet this code is easy to follow. There is no substitute for human judgement (code reviews).[1] https://en.wikipedia.org/wiki/Cyclomatic_complexity#Correlat...
Not that I'm trying to sell "ideas". I don't even know. But it's this very loose concept that floats around my mind when writing code. How many ideas are there in a stanza? Ahh too many. I should break out one of the larger ideas to happen before.
But it's only a mathematical construction and is uncomputable, just like the halting problem. In real life, for some applications a good compression algorithm like LZMA is sufficient to approximate it. But I'm not sure if it's suitable for measuring computer programs - it would still have a strong correlation to the number of lines of code.
As for correlation to number of defects, another important factor is maintainability (velocity of new features in growing programs, keeping up with infrastructure churn in mature programs). If reducing perceived complexity improves maintainability, and if measuring cyclomatic complexity despite its flaws helps reduce perceived complexity, then it's still a useful metric.
And I think that is a problem. switch()ing over an enum will give you a warning about unhandled cases in some languages. But if you start building your own lookup tables, you will be just as prone to typos like mine, except with more code, and less static analysis.
Yet the cyclomatic complexity drops from 16 to 1. I don't think that's a healthy incentive.
I think it's easy to come up with an algorithm that's better than a coin flip or counting LOC. But to be useful, the algorithm has to be on par with the judgement of the developers who check in the code and review it. Otherwise the false positives will take time away from other bug-reducing activities like code reviews and writing tests.
Of course I don't have data to prove it, but I'm convinced that every complexity checker I've encountered in the last 15 years has been a (small) net negative for the project. Maybe machine learning will improve the situation.
I don't like this type of code for exactly this reason.
switch(HTTP_METHOD) {
case PUT: processPut(...);
case POST: processPost(...);
case GET: processDelete(...); // wtf
case DELETE: processGet(...); // mate?
}
To the reader that appears to be an error even if it is precisely the thing you want to happen.In more dynamic languages, you could probably use introspection.
Lastly, this does not alleviate the association issue, but I prefer the alternative of declaring the associations in an array/map somewhere, and using map[enum_value]() instead of the switch.
A map can be a good solution though, particularly if it's something like mapping enum values to strings that is constructed via EnumClass.values().map... or something so that you know the map is a total function.
It is the same with the switch, at least in C/C++. You either have a default, or must list all the possible cases and still return something "at the bottom".
I think everyone has coded this way out of expedience and some of us have eventually messed it up too. But you could, for example, use a macro in C to only list the number once. It might not be a trade-off worth making though.
Personally, I've used reflection to do this kind of mapping of enums to functions in a language that supports reflection.
I think it's pretty clear that the number is a placeholder for something reasonable, e.g. making an association between two distinct sets of concepts. You'll still be vulnerable to copy-paste or typo issues.
> Personally, I've used reflection
Now you have two problems (and still have to maintain an association between two sets of concepts).
In my own code, I have used the enum name to map to a set of functions related to that enum value. The association is implicit in the name of the enum value and the name of the functions. No way to mess that up like this.
If it was:
case: KIND_BOX: return parseObjectOfKindBox();
It's no difference. Still repeating "Box".Turning "box" into "parseBox" using string operations and then using reflection to call the right method is an approach I'd consider in Ruby, but not in Java. It breaks IDE features like "Find Usages" and static analysis, and the code to dynamically invoke a Java method is more annoying to review than the boring repetition in my posted snippet.
In C, you could use a macro in this call to make sure that the name/number is only specified once.
I think the real issue is types aren't included in the example. I work in much higher-level languages, but if you are passing strongly typed objects thru and your switch is derived on this typing, it's probably going to be illegal in your type system to return certain results.
If your type system doesn't validate your results, then you'll be prone to the class of error you are discussing. Maybe that's common in C.
https://www.sonarsource.com/docs/CognitiveComplexity.pdf
>Switches
>A `switch` and all its cases combined incurs a single structural increment.
>Under Cyclomatic Complexity, a switch is treated as an analog to an `if-else if` chain. That is, each `case` in the `switch` causes an increment because it causes a branch in the mathematical model of the control flow.
>But from a maintainer’s point of view, a switch - which compares a single variable to an explicitly named set of literal values - is much easier to understand than an `if-else if` chain because the latter may make any number of comparisons, using any number of variables and values.
>In short, an `if-else if` chain must be read carefully, while a `switch` can often be taken in at a glance.
I disagree with them on assuming method calls being free in terms of complexity. Too much abstraction makes it difficult to follow. I've heard of this being called lasagna code since it has tons of layers (unnecessary layers).
Maybe the complexity introduced by overabstraction requires other tools to analyze? It's tricky to look at it via cylcomatic complexity or cognitive complexity since it is non-local by nature.
Great! Now we can all begin to write branchless code. Make branch prediction miss a thing of the past!
I wasted a lot of brainpower cutting down CC in my code doing terrible things like storing function names as strings in hashmaps, i.e.,
String fn = codepaths.get(if_statement_value);
obj.getClass().getDeclaredMethod(fn).invoke(param,...);
Because that could replace several 'if' checks, since function calls weren't branches. Of course, no exception handling, because you could save branches by using throws Exception.To this day, I wonder if Cyclomatic complexity obsession helped make "enterprise Java" what it is today.
Minimizing cyclomatic complexity might actually be a reasonable approach, if the complexity were accurately measured without these blind spots. For example, any indirect calls should be attributed a complexity based on the number of possible targets, and any function that can throw an exception should be counted as having at least two possible return paths (continue normally or branch to handler / re-throw exception) at each location where it's called.