Kenn ich nicht - mag ich nicht

Ein Kollege meinte einmal:

Schreib’ deinen C++ Code um, weil so etwas habe ich noch nie gesehen.

Gemeint war ein do { ... } while(0); Block.

Doch da musste ich darauf hinwirken, dass wir zuerst das Problem “So etwas habe noch nie gesehen” lösen.

Im finstersten Mittelalter war es in der Sprache C üblich, dass am Anfang einer Funktion alle Variablen deklariert wurden und am Ende der Funktion Cleanup-Code stand.

Dazwischen waren die Logik und jede Menge goto Sprünge. Am häufigsten waren Sprünge zum Ende, also zum Cleanup-Code, meist wenn ein Fehler aufgetreten war.

Deklarationsteil und Cleanup-Teil sind heute auch noch OK. Man mag mich arrogant nennen, aber ich diskutiere im 21. Jahrhundert nicht mehr über den Sinn und Unsinn von goto und vertrete die Lehrmeinung:

Jedes goto lässt sich durch eine bessere Programmstruktur vermeiden.

(Und das sage ich als jemand, der mit BASIC angefangen hat…!)

Wenn wir uns aber in einem C++ Programm befinden, bedeuten goto Anweisungen einfach nur noch Chaos, und sind per Todesstrafe verboten.

Denn während C nur rohe Daten kannte, haben wir in C++ Objekte auf dem Stack liegen, deren Initialisierung und Löschung automatisch in einem geordneten Ablauf durchgeführt werden sollen. Und goto verhindert diese Ordnung, ist ein natürlicher Feind von RAII und … einfach BÖSE!

Wie unterbricht man also einen Programmfluss und springt zum Ende einer Funktion?

Antwort: Mit break

Anstatt

int do_something()
{
  int is_ok = 1;
  
  if(step1() != SUCCESS)
  {
    is_ok = 0;
    goto so_something_ends;
  }
  if(step2() != SUCCESS)
  {
    is_ok = 0;
    goto so_something_ends;
  }
  if(step3() != SUCCESS)
  {
    is_ok = 0;
    goto so_something_ends;
  }
  
so_something_ends:
  clean_up();
  return is_ok;
}

nutzt man eine sich nicht wiederholende Schleife:


int do_something()
{
  int is_ok = 0;
  
  do
  {
    if(step1() != SUCCESS) break;
    if(step2() != SUCCESS) break;
    if(step3() != SUCCESS) break;
	
	is_ok = 1;
  } while(0);
  
  clean_up();
  return is_ok;
}

Im ersten Beispiel starten wir mit der Vermutung, dass do_something erfolgreich sein wird. Bei jedem erkannten Fehler in den Ausführungsschritten stepX() wird das Ergebnis auf falsch korrigiert und zum Ende gesprungen.

Im besseren zweiten Beispiel, gehen wir von einem Nicht-Erfolg aus und setzen den Erfolgsstatus erst, wenn alle drei Einzelschritte durchgeführt wurden.

Sollte nämlich einer der Schritte fehlschlagen, bewirkt break, dass wir aus der Schleife rausfallen, womit ìs_ok nicht mehr 1 werden kann.

Der Inhalt von do { } while(0); kann maximal einmal vollständig ausgeführt werden, aber jedes break unterbricht die Ausführung und springt zur nächsten Anweisung nach dem Ende von while(0),

Der Vorteil ist, dass innerhalb von C++ der Ablauf von Konstruktion und Zerstörung von Objekten den üblichen Verlauf nimmt. Es gibt keine Seiteneffekte, wie sie bei goto möglich sind.

Auch ich kam mit diesem Wissen nicht auf die Welt, sondern habe es mir im Laufe meiner Karriere als Programmierer angeeignet.

Fazit: Wenn ihr mal eine Codepassage seht, die ihr nicht versteht, so versucht doch einfach mal sie zu verstehen.

Natürlich kann dabei auch rauskommen, dass die Lösung nicht ideal ist und durch etwas anderes ersetzt werden sollte.

Aber keinesfalls sollte man den Weg ablehnen, nur weil man ihn zuvor noch nicht gesehen hat und ihn nicht versteht.


Tatsächlich habe ich dieses Wissen aus VBScript erlernt. QBASIC und das große VB erlaubten goto, das ich früher auch oft eingesetzt hatte, aber VBScript hat weder Code-Labels noch goto implementiert. Da bleibt einem also gar nichts anderes übrig als Alternativen einzusetzen.

Mein Kollege hätte übrigens gerne den manuellen Aufruf von clean_up() in jedem if Block mit nachfolgendem return gesehen.

Hier komme ich mit einer anderen Weisheit: Redundanzen vermeiden wo möglich. Denn genau diese Wiederholungen führen zu Fehlern bei der Codeerweiterung. Da wird dann immer einen Block vergessen… (auch das ist mir schon viel zu oft passiert.)

Nachtrag: Mir ist schon klar, dass gute C++ Compiler einfache goto Codes (wie den Beispielcode oben) auch korrekt abwickeln können. Mit steigender Komplexität ist diese Aussage jedoch nicht mehr zutreffend. Und man soll sich keinen Stil angewöhnen, der Fehler begünstigt.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!