Cyclomatic Complexity will be high for methods with a high number of decision statements including if/while/for statements. So how do we improve on it?
I am handling a big project where I am supposed to reduced the CC for methods that have CC > 10. And there are many methods with this problem. Below I will list down some eg of code patterns (not the actual code) with the problems I have encountered. Is it possible that they can be simplified?
Example of cases resulting in many decision statements:
Case 1)
if(objectA != null) //objectA is a pass in as a parameter
{
objectB = doThisMethod();
if(objectB != null)
{
objectC = doThatMethod();
if(objectC != null)
{
doXXX();
}
else{
doYYY();
}
}
else
{
doZZZ();
}
}
Case 2)
if(a < min)
min = a;
if(a < max)
max = a;
if(b > 0)
doXXX();
if(c > 0)
{
doYYY();
}
else
{
doZZZ();
if(c > d)
isTrue = false;
for(int i=0; i<d; i++)
s[i] = i*d;
if(isTrue)
{
if(e > 1)
{
doALotOfStuff();
}
}
}
Case 3)
// note that these String Constants are used elsewhere as diff combination,
// so you can't combine them as one
if(e.PropertyName.Equals(StringConstants.AAA) ||
e.PropertyName.Equals(StringConstants.BBB) ||
e.PropertyName.Equals(StringConstants.CCC) ||
e.PropertyName.Equals(StringConstants.DDD) ||
e.PropertyName.Equals(StringConstants.EEE) ||
e.PropertyName.Equals(StringConstants.FFF) ||
e.PropertyName.Equals(StringConstants.GGG) ||
e.PropertyName.Equals(StringConstants.HHH) ||
e.PropertyName.Equals(StringConstants.III) ||
e.PropertyName.Equals(StringConstants.JJJ) ||
e.PropertyName.Equals(StringConstants.KKK))
{
doStuff();
}
The size of your functions and procedures is almost all that matters when you have to manage program complexity. Limit your functions and methods to a minimum. If they're getting too big, find a technique to break them up into smaller pieces. Remember, code simplicity is fundamental to a well-written, clean code.
For most routines, a cyclomatic complexity below 4 is considered good; a cyclomatic complexity between 5 and 7 is considered medium complexity, between 8 and 10 is high complexity, and above that is extreme complexity.
Avoid use of switch/case statements in your code. Use Factory or Strategy design patterns instead. Complexity of 8 (1 for each CASE and 1 for method itself). Refactoring using Factory design pattern and complexity changed to 1.
Reduce the Number of Decision Structures You might consider this one a no-brainer. If the decision structures—especially if-else and switch case—are what cause more branches in the code, it stands to reason that you should reduce them if you want to keep cyclomatic complexity at bay.
Case 1 - deal with this simply by refactoring into smaller functions. E.g. the following snippet could be a function:
objectC = doThatMethod();
if(objectC != null)
{
doXXX();
}
else{
doYYY();
}
Case 2 - exactly the same approach. Take the contents of the else clause out into a smaller helper function
Case 3 - make a list of the strings you want to check against, and make a small helper function that compares a string against many options (could be simplified further with linq)
var stringConstants = new string[] { StringConstants.AAA, StringConstants.BBB etc };
if(stringConstants.Any((s) => e.PropertyName.Equals(s))
{
...
}
You should use the refactoring Replace Conditional with Polymorphism to reduce CC.
The difference between conditional an polymorphic code is that the in polymorphic code the decision is made at run time. This gives you more flexibility to add\change\remove conditions without modifying the code. You can test the behaviors separately using unit tests which improves testability. Also since there will be less conditional code means that the code is easy to read and CC is less.
For more look into behavioral design patterns esp. Strategy.
I would do the first case like this to remove the conditionals and consequently the CC. Moreover the code is more Object Oriented, readable and testable as well.
void Main() {
var objectA = GetObjectA();
objectA.DoMyTask();
}
GetObjectA(){
return If_All_Is_Well ? new ObjectA() : new EmptyObjectA();
}
class ObjectA() {
DoMyTask() {
var objectB = GetObjectB();
var objectC = GetObjectC();
objectC.DoAnotherTask(); // I am assuming that you would call the doXXX or doYYY methods on objectB or C because otherwise there is no need to create them
}
void GetObjectC() {
return If_All_Is_Well_Again ? new ObjectC() : new EmptyObjectC();
}
}
class EmptyObjectA() { // http://en.wikipedia.org/wiki/Null_Object_pattern
DoMyTask() {
doZZZZ();
}
}
class ObjectC() {
DoAnotherTask() {
doXXX();
}
}
class EmptyObjectB() {
DoAnotherTask() {
doYYY();
}
}
In second case do it the same was as first.
In the third case -
var myCriteria = GetCriteria();
if(myCriteria.Contains(curretnCase))
doStuff();
IEnumerable<Names> GetCriteria() {
// return new list of criteria.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With