Kenn ich nicht - mag ich nicht
« | 09 Nov 2018 | »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
1int do_something() 2{ 3 int is_ok = 1; 4 5 if(step1() != SUCCESS) 6 { 7 is_ok = 0; 8 goto so_something_ends; 9 } 10 if(step2() != SUCCESS) 11 { 12 is_ok = 0; 13 goto so_something_ends; 14 } 15 if(step3() != SUCCESS) 16 { 17 is_ok = 0; 18 goto so_something_ends; 19 } 20 21so_something_ends: 22 clean_up(); 23 return is_ok; 24} 25
nutzt man eine sich nicht wiederholende Schleife:
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 herausfallen, womit is_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.