Comment by bunderbunder
12 hours ago
Overall cyclomatic complexity is a useful metric, but it does have one shortcoming when used with modern languages: it was invented before polymorphism really became a thing.
That means that it really only counts explicit branching. So, for example, in an OO language like C#, calling a virtual method doesn’t increment cyclomatic complexity even though the method invocation could go down many code paths. Potentially thousands if you’re dealing with a common interface like IEnumerable. If you’re working on a library then the number of potential code paths in this kind of situation is unbounded.
As an aside, it’s interesting to think how it might apply to a language like Smalltalk that doesn’t even have if or switch statements.
OO isn’t the only monkey wrench, either. Higher-order functions also introduce forms of branching that cyclomatic complexity doesn’t measure.
Again that doesn’t make it a useless metric. Just don’t think that a cyclomatic complexity limit in your codebase is some sort of maintainability panacea. Some of the least comprehensible functions I’ve deciphered had quite low cyclomatic complexities.
In reading the article, it did feel somewhat wrong in a way that I couldn't quite describe. But after reading this comment, maybe it just seems old and maybe obsolete.
Taking the example provided in the article, I don't feel like the new code is meaningfully less complex. In fact, since it added some additional indirection, I could argue it's slightly more complex.
The core code with the nested if statements is something that I would probably refactor in some other way entirely. Maybe by taking advantage of other language features. It is a toy example so it's hard to say but that's part that feels like it needs simplification and untouched in this example.
Agreed. I also don’t really agree that the refactored code is any easier to test. ProcessOrder’s behavior hasn’t changed, only its implementation details, so the minimal test surface is the same for both: just test ProcessOrder.
You could additionally test the three helper functions. But the original tests against ProcessOrder would still be needed for completeness, so they wouldn’t necessarily add much except in an Uncle Bob style, “He who dies with the largest burden of gratuitous micro-tests wins,” sort of way.
Now if I really wanted to make that code easier to test, I’d instead be looking into ways to make the whole thing less stateful. Temporal coupling is much more confusing than if statements.
It also depends how leakproof the abstractions are. Goto statement and if-statement might both compile to similar jump instructions in assembly, but it's abstracted away. Similarly dynamic dispatch can abstract the branching so it can be ignored.
> Some of the least comprehensible functions I’ve deciphered had quite low cyclomatic complexities.
In agreement with your post, this research that measures cognitive load via EEG and time spent shows that the metrics we use for complexity and readability are only partial matches to what is going on: https://pmc.ncbi.nlm.nih.gov/articles/PMC9942489/