Reducing cyclomatic complexity in c++

Viewed 317

I want to ask that for a simple if else statement with multiple conditions how can the cyclomatic complexity be reduced ? For istance:

   std::string x; 
   if(cond)
     x = "Value1";
   else if(cond2)
   {
    if(cond3)
       x = "Value2";
    else if(cond4)
       x = "Value3";
    else
       x = "Value4";  
   }
   else if(cond5)
     x = "Value6";
   else if(cond6)
     x = "Value7";
       
   


   
4 Answers

I had to google it. Wikipedia says:

Cyclomatic complexity is a software metric used to indicate the complexity of a program. It is a quantitative measure of the number of linearly independent paths through a program's source code.

You can draw a table or graph:

                            *
                            |
                   ----------------------
                  |      |      |       |
                cond   cond2  cond5   cond6
                  |      |      |       |
                x+=1     |    x+=5    x+=6
                         |
                 -----------------
                 |       |       |
               cond3   cond4   else
                 |       |       |
               x+=2    x+=3    x+=4

Now you can see that no two branches lead to the same result. If we could find branches that lead to the same result then we could try to reduce the number of paths. Also without more information I have to assume that all conditions are independent. If there are relations between them, for example cond2 => cond3 then you could reduce the number of branches.

#include<stdio.h>

int main(){ 
    bool cond,cond1,cond2,cond3,cond4,cond5,cond6;
    
    cond6=true;
    int x=10;
    x+=cond?1:(cond2?(cond3?2:(cond4?3:4)):(cond5?5:(cond6?6:0)));
    
    printf("%d",x);
}

You can use the bits in an unsigned int to declare properties, and combine those properties to define masks. A dispatch function uses the x value and a flag value to test the bits and respond accordingly.

#include <cstdint>
#include <iostream>

// Properties
static constexpr const auto C1 = 1u << 0;
static constexpr const auto C2 = 1u << 1;
static constexpr const auto C3 = 1u << 2;
static constexpr const auto C4 = 1u << 3;
static constexpr const auto C5 = 1u << 4;
static constexpr const auto C6 = 1u << 5;

// Masks (combination of properties)
static constexpr const auto C2C3 = C2 | C3;
static constexpr const auto C2C4 = C2 | C4;

// Dispatch operation based on flag (will jump, not branch)
int dispatch(int x, std::uint32_t flags) {
  switch (flags) {
    case C1:
      return x + 1;
    case C2:
      return x + 4;
    case C2C3:
      return x + 2;
    case C2C4:
      return x + 3;
    case C5:
      return x + 5;
    case C6:
      return x + 6;
    // add more cases as needed...

    default:
      // anything not specified is considered an error
      throw std::runtime_error("invalid flags");
  }
}

#define ASSERT(pred)                       \
  do {                                     \
    if (!(pred)) {                         \
      std::cerr << "expected " #pred "\n"; \
    } else {                               \
      std::cout << "ok " #pred "\n";       \
    }                                      \
  } while (false)

#define EXPECT_THROW(pred)                                \
  do {                                                    \
    bool thrown = false;                                  \
    try {                                                 \
      std::cout << (pred) << "\n";                        \
    } catch (...) {                                       \
      thrown = true;                                      \
    }                                                     \
    if (thrown) {                                         \
      std::cout << "ok " #pred "\n";                      \
    } else {                                              \
      std::cerr << "exception not thrown in " #pred "\n"; \
    }                                                     \
  } while (false)

int main() {
  // Only these should succeed
  ASSERT(dispatch(0, C1) == 1);
  ASSERT(dispatch(0, C2) == 4);
  ASSERT(dispatch(0, C2C3) == 2);
  ASSERT(dispatch(0, C2C4) == 3);
  ASSERT(dispatch(0, C5) == 5);
  ASSERT(dispatch(0, C6) == 6);

  // Anything else should be a failure (there are 2^6 = 64 combinations and only
  // 6 should succeed)... only a few are presented
  EXPECT_THROW(dispatch(0, C1 | C2));
  EXPECT_THROW(dispatch(0, C1 | C3));
  EXPECT_THROW(dispatch(0, C3 | C4));
  EXPECT_THROW(dispatch(0, C3));
}

Sample run:

$ clang++ example.cpp -std=c++2a -Wall -Wextra
$ ./a.out 
ok dispatch(0, C1) == 1
ok dispatch(0, C2) == 4
ok dispatch(0, C2C3) == 2
ok dispatch(0, C2C4) == 3
ok dispatch(0, C5) == 5
ok dispatch(0, C6) == 6
ok dispatch(0, C1 | C2)
ok dispatch(0, C1 | C3)
ok dispatch(0, C3 | C4)
ok dispatch(0, C3)

The dispatch function can be easily modified to add more cases. It would be more beneficial if the properties/flags had a more meaningful name.

Say this whole block of code that is complex is in FunctionMain(), you could move this code into another function and just call that function. That way the complexity is moved to lower level function and main code readable.

Another way is you split the segments in this code into functions which are readable.

Related