Artur Södler Software-Qualität

McCabe und seine blinden Jünger


Thomas J. McCabe definiert die Komplexität von Programmteilen anhand der darin enthaltenen Verzweigungen.

Sehen Sie einmal genauer hin: Eine Verzweigung bewirkt zwei verschiedene möglche Zustände des Programmzeigers. Davon abhängig werden unterschiedliche weitere Befehle verarbeitet. Wo ist der Unterschied zu zwei verschiedenen möglchen Zuständen einer Variablen? Vergleichen Sie:

bool DatumGueltig (int Tag, int Monat, int Jahr) {
   if (Monat < 1 || Monat > 12) return false;
   int Tage = 31;
   if (Monat == 4 || Monat == 6
      || Monat == 9 || Monat == 11)
      Tage = 30;
   if (Monat == 2)
      if (Jahr % 4)
         Tage = 28;
      else
         Tage = 29;
   if (Tag < 1 || Tag > Tage) return false;
   return true;
}

bool DatumGueltig (int Tag, int Monat, int Jahr) {
   unsigned Tage = 28 + (0x3bbeecc >> Monat & 3)
      + ((Jahr & 3) - 1 & 16)
      / ((Monat - 2) & 15 + 16);
   return (
      ((unsigned) Monat -1) / 12
      + ((unsigned) Tag -1) / Tage
      ) == 0;
}

Die linke Fassung hat auch noch versteckte Verzweigungen über die booleschen Operatoren ||. Die rechte Fassung kommt komplett ohne Verzweigungen aus. Kürzer ist sie auch. Aber ist sie wartbarer???

Was McCabe eigentlich damit sagen wollte, war: Ab einem gewissen Maß an Komplexität muss Schluss sein. Dieses Maß an Komplexität über die Verzweigungen zu definieren, war ein legitimer Versuch, und nicht einmal schlecht. Aber allgemeingültig ist es auch nicht.

Viele weitere gute Ratschläge werden Softwareentwicklern im Informatik-Studium mitgegeben. Einige davon sind kompletter Unsinn. Nehmen wir zum Beispiel nur einmal das Dogma, goto-Anweisungen seien schlechter Programmierstil. Warum? Im linken Beispiel stecken doch auch zweie drin, und keiner merkt es. Unter dem Deckmäntelchen "return" wird eine Funktion aus der Mitte heraus beendet. Sollen wir etwa gemäß dem Dogma Exceptions in allen Programmiersprachen abschaffen?

Natürlich nicht. Der Grund ist ein anderer: Oft werden in Programmteilen "oben" Ressourcen angelegt und "unten" freigegeben. Sprünge bewirken dann oft, dass das "Aufräumen" vergessen wird. Seitdem die objektorientierte Programmierung diesem Problem mit Destruktoren entgegenwirkt, dürfen wir wieder ungestraft Exceptions verwenden. Und auch return und goto.

Blind angewendete Metriken führen nicht selten zu Fehleinschätzungen. Und besonders wenn Entwickler erfahren, mit welchen Maßstäben sie beurteilt werden, entstehen Stilblüten wie das Beispiel oben rechts.

Zum Abschluss noch ein Beispiel für die einfachste Methode, die Wartbarkeit zu erhöhen: Dokumentation.

bool DatumIstGueltig (int Tag, int Monat, int Jahr) {
   // Tag, Monat und Jahr gegen Grenzen testen.
   // Die Funktion berücksichtigt nur aktuelle Datumswerte, ignoriert also die
   // Säkularjahre 1900 und 2100 des gregorianischen Kalenders (ab 15.10.1582).
   assert (Jahr > 1900 && Jahr < 2100); // Falls doch: Erweitern um Säkularjahre
   // Monat zuerst prüfen, denn die Maximalzahl Tage richtet sich nach dem Monat
   if (Monat < 1 || Monat > 12) return false;
   // Anzahl Tage des Monats berechnen:
   int Tage = 31;
   if (Monat == 4 || Monat == 6 || Monat == 9 || Monat == 11)
      Tage = 30;
   if (Monat == 2)
      if (Jahr % 4)
         Tage = 28; // Rest bei Teilung ==> kein Schaltjahr
      else
         Tage = 29; // durch 4 teilbar ohne Rest ==> Schaltjahr
   // Tag prüfen:
   if (Tag < 1 || Tag > Tage) return false;
   // Alle Prüfungen bestanden, das Datum ist okay.
   return true;
}

 
Artur Södler Software Qualität