Do iterative methods such as are commonly found in modern languages such as C#, JavaScript, and (hopefully) in Java 8 reduce cyclomatic complexity’s impact on understandability and supportability of code?
For example in C# we might have the following code:
List<String> filteredList = new List<String>();
foreach (String s in originalList){
if (matches(s)){
filteredList.add(s);
}
}
This has a simple cyclomatic complexity of 2.
We could easily re-write this as:
List<String> filteredList = originalList.where(s => matches(s));
Which has a simple cyclomatic complexity of 0.
Does this actually result in more supportable code? Is there any actual research on this topic?
4
My guess is that you just divided/moved the complexity. It decreased because you doesn’t count the implementation of .where()
in your CC.
The overall CC hasn’t really moved, your own code‘s CC decreased, only because it’s now moved to the framework’s code.
I’d say it’s more maintainable. When it’s a feature of the language, use it. It’s no a “ohh, I see, it’s a clever trick” that you’re using, only a simple inline reduce-like function.
All you are doing is highlighting a flaw in cyclomatic complexity as a metric, the complexity of the code really hasn’t changed. You have an explicit branch in the first example and need to understand there is an implied branch in the second. The second is clearer and easier to understand provided you understand the syntax, and since it uses less basic syntax that may be an issue.
1
In order to answer the question objectively, we’d need some sort of metric for maintainability. Cyclomatic complexity itself isn’t a measure of maintainability, but it is a component of some metrics that purport to measure maintainability. For example, the formula for the Maintainability Index is:
MI = 171 - 5.2 * ln(V) - 0.23 * (G) - 16.2 * ln(LOC)
where G
is the cyclomatic complexity of the code in question. Therefore, reducing the cyclomatic complexity of a piece of code does by definition improve the code’s Maintainability Index and other metrics which similarly use cyclomatic complexity.
It’s hard to say whether the kind of change you propose makes the program seem more maintainable to programmers; that probably depends on how familiar they are with (in your case) the where
method.
4
Because it has been shown that cyclomatic complexity (CC) is very strongly correlated with code size, “so much so that CC can be said to have absolutely no explanatory power of its own.” what you are really asking is whether “iterative methods such as are commonly found in modern languages such as C#, JavaScript, and (hopefully) in Java 8 reduce code size’s impact on understandability and supportability of code.”
At that point, one would hope that the answer would be obvious. It has been known for decades that shorter code is generally easier to understand, maintain, and support.
If you’re talking about the raw stat of Cyclomatic Complexity, sure. You just nuked it from 2 to 0. If you’re going by numbers, pure gain, all the way.
From a practical (read: human) perspective though, I would argue you’ve actually increased the complexity by 2. One point of that comes from the fact that now some other programmer must bring or get knowledge of fluent-syntax LINQ to understand this code.
Another point of increased difficulty comes from understanding Lambda Expressions; though a Lambda is fairly straightforward in this instance, there are some paradigm shifts that have to be made to fully appreciate them.
This case, using a.where(x => matches(x, arg))
isn’t terrible, and in all honesty is a great way to get some coworker to see and work with LINQ and Lambda expressions for the first time (I actually presented a tutorial on LINQ/Lambdas to some former coworkers using this and other sets of code, to great effect.) However, the usage of these does require some knowledge.
I recommend caution, because I’ve seen cases where the LINQ refactor is actually significantly worse to read than what that foreach
loop becomes.
Subjectively, it depends on the developer audience, if they understand lambda expressions, then;
List<String> filteredList = originalList.where(s => matches(s));
is quicker to understand, and perhaps slightly easier. I would be more concerned about using s, and matches(). Neither is self-descriptive, something like;
List<String> filteredList =
originalList.where(stringToBeTested => matchesNameTest(stringToBeTested));
Or
List<String> filteredList =
originalList.where(originalListString => matchesNameTest(originalListString));
gives the developer more meaningful information and is easier to analyze, without having to dive into the matches() function to determine what match is being performed.
Maintainability is not only about the ability to comprehend the code, but mainly about the speed and accuracy with which one can comprehend the code.
1