Artur Södler Software-Qualität

McCabe and his Blind Followers


Thomas J. McCabe defines the complexity of an algorithm by the conditional branches it contains.

Please have a close look: A conditional branch effects two possible states of the program execution pointer. Controlled by this, varying subsequent instructions are executed. What is the difference to two different states of a variable? Please compare:

bool DateValid (int Day, int Month, int Year) {
   if (Month < 1 || Month > 12) return false;
   int Days = 31;
   if (Month == 4 || Month == 6
      || Month == 9 || Month == 11)
      Days = 30;
   if (Month == 2)
      if (Year % 4)
         Days = 28;
      else
         Days = 29;
   if (Day < 1 || Day > Days) return false;
   return true;
}

bool DateValid (int Day, int Month, int Year) {
   unsigned Days = 28 + (0x3bbeecc >> Month & 3)
      + ((Year & 3) - 1 & 16)
      / ((Month - 2) & 15 + 16);
   return (
      ((unsigned) Month -1) / 12
      + ((unsigned) Day -1) / Days
      ) == 0;
}

The left version has even more conditional branches hidden by the boolean operators ||. The right version has no conditional branches at all. Even more, it is shorter. But is it more serviceable???

What McCabe was trying to say was: At a certain degree of complexity we must put an end. To define the degree of complexity by the number of conditional branches, was a legitimate attempt, and worked reasonably well. But it is not the real McCoy.

Software developers are taught many more good advices during their computer science study. Some of them are utter nonsense. Let us for instance have a look at the dogma, goto statements were an expression of a bad programming style. Why? After all, the left example contains two of them, and nobody cares. Disguised as "return", a function is terminated right from the middle. Hence we should (according to the dogma) call off exceptions in all programming languages, shouldn't we?

Of course we should not. The dogma has another reason: Often we allocate resources "above" in a program flow, and free them "below". Frequently, jumps cause the developer to forget "cleaning up". But since object oriented programming opposes destructors to that problem, we get away again using exceptions, as well as return and goto.

Blindly applied metrics quite often lead to misjudgments. Especially if developers learn to which standards they are assessed, bloopers arise like the example at the right above.

Finally a demonstration of the easiest method to increase maintainability: documentation.

bool DateIsValid (int Day, int Month, int Year) {
   // Test Day, Month and Year against its limits.
   // The function only allows for current date values, it ignores the
   // years 1900 and 2100 of the gregorian calendar (15.10.1582 onwards).
   assert (Year > 1900 && Year < 2100); // If not: extend to years divisible by 100
   // Check Month first, because the maximum number of Days depends on Month.
   if (Month < 1 || Month > 12) return false;
   // Calculate number of Days of the Month:
   int Days = 31;
   if (Month == 4 || Month == 6 || Month == 9 || Month == 11)
      Days = 30;
   if (Month == 2)
      if (Year % 4)
         Days = 28; // remainder from division ==> no leap year
      else
         Days = 29; // divisible by 4 without remainder ==> leap year
   // Check Day:
   if (Day < 1 || Day > Days) return false;
   // All tests passed, the date is okay.
   return true;
}

 
Artur Södler Software Qualität