Consider the following method (in Java - and please just ignore the content):
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (getClass() != object.getClass()) {
return false;
}
if (hashCode() != object.hashCode()) {
return false;
}
return true;
}
I have some plugin that calculates: eV(g)=5
and V(g)=5
- that is, it calculates Essential and common CC.
Now, we can write the above method as:
public boolean equals2(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
return hashCode() == object.hashCode();
}
and this plugin calculates eV(g)=3
and V(g)=3
.
But how I do understand CC, the values should be the same! CC is not about counting the lines of code, but the independent paths. Therefore, joining two if
in one line does not really reduces CC. In fact, it only can make things less readable.
Am I right?
EDIT
Forgot to share this small convenient table for calculating CC quickly: Start with a initial (default) value of one (1). Add one (1) for each occurrence of each of the following:
if
statementwhile
statementfor
statementcase
statementcatch
statement&&
and||
boolean operations?:
ternary operator and?:
Elvis operator.?.
null-check operator
EDIT 2
I proved that my plugin is not working well, since when I inline everything in one line:
public boolean equals(Object object) {
return this == object || object != null && getClass() == object.getClass() && hashCode() == object.hashCode();
}
it returns CC == 1, which is clearly wrong. Anyway, the question remains: is CC reduced
[A] 5 -> 4, or
[B] 4 -> 3
?
Long story short...
Your approach is a good approach to calculate CC, you just need to decide what you really want to do with it, and modify accordingly, if you need so.
For your second example, both CC=3 and CC=5 seem to be good.
The long story...
There are many different ways to calculate CC. You need to decide what is your purpose, and you need to know what are the limitations of your analysis.
The original definition from McCabe is actually the cyclomatic complexity (from graph theory) of the control flow graph. To calculate that one, you need to have a control flow graph, which might require a more precise analysis than your current one.
Static analyzers want to calculate metrics fast, so they do not analyze the control flow, but they calculate a complexity metric that is, say, close to it. As a result, there are several approaches...
For example, you can read a discussion about the CC metric of SonarQube here or another example how SourceMeter calculates McCC here.
What is common, that these tools count conditional statements, just like you do. But, these metrics wont be always equal with the number of independent execution paths... at least, they give a good estimation.
Two different ways to calculate CC (McCabe and Myers' extension):
If your goal is to estimate the number of test cases, V2 is the one for you. But, if you want to have a measure for code comprehension (e.g. you want to identify methods that are hard to maintain and should be simplified in the code), V1 is easier to calculate and enough for you.
In addition, static analyzers measure a number of additional complexity metrics too (e.g. Nesting Level).