Grundelemente strukturierter Programmierung

Für die Berechnung der Essentiellen Komplexität wir der Kontrollflußgraph des Modules reduziert in dem alle Grundelemente der strukturierten Programmierung entfernt werden, bis keine mehr vorkommen.


Dabei spielt es keine Rolle mit welchen Sprachelementen diese Strukturelemente erzeugt werden.

v(G) = 2; ev(G) = 1
 
So kann z.B. der nebenstehende Graph in C/C++ sowohl als while-Schleife implementiert werden als auch durch die Verwendung von goto's
Beispiel 1
    while ( p ) {
       A;
    }
Beispiel 2
    M1:
    if ( !p ) goto M2;
    A;
    goto M1;
    M2:


Erhöhte Essentielle Komplexität durch zuammengesetzte Prädikate

ev(G) wird z.T. auch durch die Verwendung Boolscher Operatoren in Entscheidungen erhöht. Diese Operatoren als Elemente unstrukturierter Programmierung zu "bestrafen" ist problematisch. Außerdem ist problematisch das die Verwendung dieser Operatoren nicht immer gleich behandelt wird.
Dieses Problem tritt nur bei Programmiersprachen auf, die - wie C/C++ - zusammengesetzte Prädikate der Effektivität wegen lazy evaluieren.

||-Operator in Entscheidungen

v(G) = 3
ev(G) = 3
if ( p || q ) {
   A;
}
v(G) = 3
ev(G) = 3
if ( p || q ) {
   A;
} else {
   B;
}

&&-Operator in Entscheidungen

Hier fällt vor allem die Ungleichbehandlung von Entscheidungen mit und ohne else-Zweig auf.

v(G) = 3
ev(G) = 1
if ( p && q ) {
   A;
}
v(G) = 3
ev(G) = 3
if ( p && q ) {
   A;
} else {
   B;
}

&&- und ||-Operatoren in Zuweisungen

In Zuweisungen führen Boolsche Operatoren nicht zur Erhöhung von ev(G) - werden also nicht als unstrukturiert gewertet.
Dadurch können Kontrollstrukturen aus den vorangegangenen Beispielen durch Einführung einer neuen Variablen so umgeschrieben werden, daß sie zwar semantisch äquivalent sind aber eine geringere Essentielle Komplexität besitzen.
Ob der Code des umgeschriebenen Modules dadurch wirklich strukturierter geworden ist ist für mich noch offen.

v(G) = 3
ev(G) = 1
b = p || q || r;
v(G) = 3
ev(G) = 1
b = p && q && r;

Hier nun ein Beispiel für die Umformung von zusammengesetzten Prädikaten in Entscheidungen mit Hilfe von Zuweisungen zur Verringerung der Essentiellen Komplexität.

v(G) = 4
ev(G) = 4
if ( p || q || r ) {
   A;
} else {
   B;
}
v(G) = 4
ev(G) = 1 (!!)
b = p || q || r;
if ( b ) {
   A;
} else {
   B;
}

Erhöhte Essentielle Komplexität durch unstrukturierte Programmierung

Nun noch einige Beispiele für Graphen mir erhöhten ev(G)-Werten durch Sprunganweisungen, also Sprachelemente die tatsächlich zum unstrukturierten Programmieren gehören. Zu Sprunganweisungen gehöhren aber nicht nur die goto's zu selbst definierten Marken sondern auch die Sprunganweisungen mit vordefinierten Marken:

  • der Sprung ans Ende der Funktion zum Verlassen einer Funktion (return)
  • der Sprung an den Beginn einer Schleife zur "vorzeitigen" Fortsetzen (continue)
  • und der Sprung ans Ende einer "vorzeitig" zu verlassenden Schleife (break)

Bei diesen Strukturen muß beachtet werden, daß sie nur dann ev(G) erhöhen wenn sie nicht in die Muster der Struktirierten Programmierung passen. Dazu passt das Beispiel vom Anfang: eine mit gotos simulierte Schleife.

Zum break in C/C++ ist zu sagen, daß im switch-Statement das break keine unstrukturierte Sprunganweisung ist, sondern im Gegenteil, das Fehlen zu unstruktiruerten Strukturen führt.

In den folgenden Beispielen sind jeweils die unstrukturierten Programmteile und eine mögliche Umformung angegeben. Die umgeformten Varianten sind nicht notwendigerweise wirklich "schöner". Ich denke in Maßen angewendet führen die definierten Sprunganweisungen zu kompakterem und übersichtlicherem Code.

Schleifen mit mehreren Fortsetzungen (continue)

v(G) = 3
ev(G) = 3
while ( p ) {
   ...
   if ( q ) { 
      continue;
   }
   A;
   ...
}
v(G) = 3
ev(G) = 1
while ( p ) {
   ...
   if ( !q ) { 
      A;
      ...
   }
}

Endlosschleifen

v(G) = 3 (2)
ev(G) = 3 (2)
while ( true ) {
    A;
    if ( p ) {
        break;
    }
    B;
}
v(G) = 3
ev(G) = 1
// wenn B p nicht verändert
p = false;
while ( !p ) {
    A;
    if ( !p ) {
       B; 
    }
}

Schleifen mit mehreren Ausgängen (break)

v(G) = 3
ev(G) = 3
while ( p ) {
   A;
   if ( q ) {
      B;
      break;
   }
   C;
}

Die Umformungen dieses Codes ist für
den allgemeinen Falle ziehmlich aufwendig,
da nicht ersichtlich ist wo p und q
verändert werden: in A, B oder C.

Es wird für das Beispiel angenommen,
das p in B nicht verändert
wird und q nicht in C verändert wird.

v(G) = 4
ev(G) = 1
r = p; // r neue variable
while ( r ) {
   A;
   if ( q ) {
      B;
   } else {
      C;
   }
   r = !q && p;
}

In diesem Bsp. konnte ev(G) nur
auf Kosten eines höheren v(G)-Wertes
verbessert werden.
Unter bestimmten Bedingungen kann
man auch auf die neue Variable
verzichten und die Entscheidung
wieder in den Kompf der Schleife
verlegen.

Das geht aber nur auf Kosten
von ev(G) da dann wieder zusammen-
gesetzte Prädikate in Entscheidungen
auftauchen.

v(G) = 4
ev(G) = 3

// wenn q in A bestimmt wird
q = true;
while ( p && !q ) {
   A;
   if ( q ) {
      B;
   } else {
      C;
   }
}

Damit ist zwar das break entfernt
worden, aber die Essentielle Komplexität
immer noch genau so hoch. Zusätzlich hat
sich die Zyklomatische Komplexität erhöht