Wenn der Code-Fluss so ist:
if(check())
{
...
...
if(check())
{
...
...
if(check())
{
...
...
}
}
}
Ich habe allgemein gesehen, wie dies funktioniert, um den oben genannten unordentlichen Codefluss zu vermeiden:
do {
if(!check()) break;
...
...
if(!check()) break;
...
...
if(!check()) break;
...
...
} while(0);
Was sind einige bessere Möglichkeiten, um diese Umgehung / diesen Hack zu vermeiden, damit er zu einem Code auf höherer Ebene (Branchenebene) wird?
Vorschläge, die sofort einsatzbereit sind, sind willkommen!
goto
- aber ich bin sicher, jemand wird mich dafür markieren, dass ich das vorschlage, also schreibe ich keine Antwort auf diesen Effekt. LANG zu habendo ... while(0);
scheint das Falsche zu sein.goto
, seien Sie ehrlich und tun Sie es im Freien, verstecken Sie es nicht mitbreak
unddo ... while
goto
Rehabilitationsbemühungen geben. :)Antworten:
Es wird als akzeptable Praxis angesehen, diese Entscheidungen in einer Funktion zu isolieren und
return
stattdessen s zu verwendenbreak
s zu verwenden. Während alle diese Überprüfungen der gleichen Abstraktionsebene wie die Funktion entsprechen, ist dies ein logischer Ansatz.Beispielsweise:
quelle
return
ist sauberer, weil jeder Leser sofort merkt, dass es richtig funktioniert und was es tut. Mitgoto
man muss schauen , um zu sehen , was es ist, und um sicher zu sein , dass keine Fehler gemacht wurden. Die Verkleidung ist der Vorteil.forceinline
.Es gibt Zeiten, in denen die Verwendung
goto
tatsächlich die RICHTIGE Antwort ist - zumindest für diejenigen, die nicht in der religiösen Überzeugung erzogen sind, dass "goto
niemals die Antwort sein kann, egal was die Frage ist" - und dies ist einer dieser Fälle.Dieser Code verwendet den Hack von nur
do { ... } while(0);
zum Verkleiden einesgoto
alsbreak
. Wenn Sie verwenden werdengoto
, dann seien Sie offen darüber. Es macht keinen Sinn, den Code schwerer zu lesen.Eine besondere Situation ist nur dann, wenn Sie viel Code mit recht komplexen Bedingungen haben:
Es kann tatsächlich klarer machen, dass der Code korrekt ist, wenn keine so komplexe Logik vorhanden ist.
Bearbeiten: Zur Verdeutlichung schlage ich keineswegs die Verwendung
goto
als allgemeine Lösung vor. Aber es gibt Fälle, in denengoto
eine bessere Lösung als andere Lösungen vorliegt.Stellen Sie sich zum Beispiel vor, wir sammeln einige Daten und die verschiedenen Bedingungen, auf die getestet wird, sind eine Art "Dies ist das Ende der gesammelten Daten" - was von einer Art von "Weiter / Ende" -Markierungen abhängt, die je nach Ort variieren Sie befinden sich im Datenstrom.
Wenn wir fertig sind, müssen wir die Daten in einer Datei speichern.
Und ja, es gibt oft andere Lösungen, die eine vernünftige Lösung bieten können, aber nicht immer.
quelle
goto
mag einen Platz haben,goto cleanup
tut es aber nicht. Die Bereinigung erfolgt mit RAII.goto
ist nie die richtige Antwort. Ich würde mich freuen, wenn es einen Kommentar gäbe ...goto
weil man darüber nachdenken muss, es zu verwenden / ein Programm zu verstehen, das es verwendet ... Mikroprozessoren hingegen bauen darauf aufjumps
undconditional jumps
... das Problem liegt also bei einigen Menschen, nicht bei Logik oder etwas anderem.goto
. RAII ist nicht die richtige Lösung, wenn Fehler erwartet werden (keine Ausnahme), da dies ein Missbrauch von Ausnahmen wäre.Sie können ein einfaches Fortsetzungsmuster mit einer
bool
Variablen verwenden:Diese Ausführungskette wird beendet, sobald
checkN
a zurückgegeben wirdfalse
. Keine weiterencheck...()
Anrufe würden durch Kurzschließen der durchgeführt werden&&
Betreibers . Darüber hinaus sind optimierende Compiler intelligent genug, um zu erkennen, dass es sich bei der EinstellunggoOn
auffalse
eine Einbahnstraße handelt, und die fehlendengoto end
für Sie einzufügen . Infolgedessen wäre die Leistung des obigen Codes mit der einesdo
/ identischwhile(0)
, nur ohne dass die Lesbarkeit schmerzhaft beeinträchtigt würde.quelle
if
Bedingungen sehen sehr verdächtig aus.if
egal wasgoOn
war, selbst wenn ein früher fehlschlug (im Gegensatz zum Springen / Ausbrechen) ... aber ich habe gerade einen Test durchgeführt und VS2012 war zumindest klug genug, um sowieso alles nach dem ersten Falsch kurzzuschließen. Ich werde das öfter benutzen. Anmerkung: Wenn Sie verwendengoOn &= checkN()
danncheckN()
immer noch laufen, wenngoOn
warfalse
zu Beginn desif
(dh das nicht tun).if
s.Versuchen Sie, den Code in eine separate Funktion (oder möglicherweise in mehrere) zu extrahieren. Kehren Sie dann von der Funktion zurück, wenn die Prüfung fehlschlägt.
Wenn es zu eng mit dem umgebenden Code gekoppelt ist und Sie keine Möglichkeit finden, die Kopplung zu verringern, sehen Sie sich den Code nach diesem Block an. Vermutlich werden einige von der Funktion verwendete Ressourcen bereinigt. Versuchen Sie, diese Ressourcen mit einem RAII- Objekt zu verwalten. Ersetzen Sie dann jeden zwielichtigen
break
durchreturn
(oderthrow
, falls dies angemessener ist) und lassen Sie den Destruktor des Objekts für Sie aufräumen.Wenn der Programmablauf (notwendigerweise) so schnörkellos ist, dass Sie wirklich einen benötigen
goto
, verwenden Sie diesen, anstatt ihn seltsam zu verkleiden.Wenn Sie Codierungsregeln haben, die blind verbieten
goto
, und Sie den Programmablauf wirklich nicht vereinfachen können, müssen Sie ihn wahrscheinlich mit Ihremdo
Hack verschleiern .quelle
goto
ob dies wirklich eine bessere Option ist.TLDR : RAII , Transaktionscode (nur Ergebnisse festlegen oder , wenn dieser bereits berechnet wurde) und Ausnahmen.
Lange Antwort:
In C besteht die beste Vorgehensweise für diese Art von Code darin, dem Code ein EXIT / CLEANUP / other- Label hinzuzufügen, in dem die lokalen Ressourcen bereinigt werden und ein Fehlercode (falls vorhanden) zurückgegeben wird. Dies ist eine bewährte Methode, da der Code auf natürliche Weise in Initialisierung, Berechnung, Festschreiben und Zurückgeben aufgeteilt wird:
In C ist in den meisten Codebasen der Code
if(error_ok != ...
undgoto
normalerweise hinter einigen praktischen Makros versteckt (RET(computation_result)
,ENSURE_SUCCESS(computation_result, return_code)
usw.).C ++ bietet zusätzliche Tools über C :
Die Bereinigungsblockfunktion kann als RAII implementiert werden. Dies bedeutet, dass Sie nicht mehr den gesamten
cleanup
Block benötigen und dass der Clientcode frühe Rückgabeanweisungen hinzufügen kann.Sie werfen, wann immer Sie nicht weitermachen können, und verwandeln alle
if(error_ok != ...
in direkte Anrufe.Äquivalenter C ++ - Code:
Dies ist eine bewährte Methode, weil:
Es ist explizit (das heißt, während die Fehlerbehandlung nicht explizit ist, ist der Hauptfluss des Algorithmus)
Es ist einfach, Client-Code zu schreiben
Es ist minimal
Es ist einfach
Es gibt keine sich wiederholenden Codekonstrukte
Es werden keine Makros verwendet
Es werden keine seltsamen
do { ... } while(0)
Konstrukte verwendetEs kann mit minimalem Aufwand wiederverwendet werden (dh wenn ich den Aufruf in
computation2();
eine andere Funktion kopieren möchte , muss ich nicht sicherstellen, dass ichdo { ... } while(0)
im neuen Code ein, kein#define
goto-Wrapper-Makro und kein Bereinigungsetikett hinzufüge oder noch etwas).quelle
shared_ptr
Mit einem benutzerdefinierten Deleter können viele Dinge erledigt werden. Noch einfacher mit Lambdas in C ++ 11.namespace xyz { typedef shared_ptr<some_handle> shared_handle; shared_handle make_shared_handle(a, b, c); };
In diesem Fall (mit demmake_handle
Festlegen des richtigen Löschertyps bei der Konstruktion) deutet der Name des Typs nicht mehr darauf hin, dass es sich um einen Zeiger handelt .Der Vollständigkeit halber füge ich eine Antwort hinzu. Eine Reihe anderer Antworten wies darauf hin, dass der große Bedingungsblock in eine separate Funktion aufgeteilt werden könnte. Es wurde jedoch auch mehrfach darauf hingewiesen, dass dieser Ansatz den bedingten Code vom ursprünglichen Kontext trennt. Dies ist ein Grund, warum der Sprache in C ++ 11 Lambdas hinzugefügt wurden. Die Verwendung von Lambdas wurde von anderen vorgeschlagen, es wurde jedoch keine explizite Probe bereitgestellt. Ich habe eine in diese Antwort eingefügt. Was mir auffällt, ist, dass es sich
do { } while(0)
in vielerlei Hinsicht sehr ähnlich anfühlt - und vielleicht bedeutet das, dass es immer noch einegoto
Verkleidung ist ...quelle
Sicher nicht die Antwort, sondern eine Antwort (der Vollständigkeit halber)
Anstatt :
Sie könnten schreiben:
Dies ist immer noch ein goto in der Verkleidung, aber zumindest ist es nicht eine Schleife mehr. Das heißt, Sie müssen nicht sehr sorgfältig prüfen, ob irgendwo im Block keine weiteren versteckt sind.
Das Konstrukt ist auch so einfach, dass Sie hoffen können, dass der Compiler es wegoptimiert.
Wie von @jamesdlin vorgeschlagen, können Sie dies sogar hinter einem Makro wie verbergen
Und benutze es gerne
Dies ist möglich, weil die C-Sprachsyntax eine Anweisung nach einem Wechsel erwartet, keinen Block in Klammern, und Sie dieser Anweisung eine Fallbezeichnung hinzufügen können. Bis jetzt habe ich nicht den Sinn gesehen, das zuzulassen, aber in diesem speziellen Fall ist es praktisch, den Schalter hinter einem schönen Makro zu verstecken.
quelle
define BLOCK switch (0) case 0:
und es wie verwendenBLOCK { ... break; }
.Ich würde einen Ansatz empfehlen, der Mats Antwort abzüglich des Unnötigen ähnelt
goto
. Setzen Sie nur die bedingte Logik in die Funktion ein. Jeder Code, der immer ausgeführt wird, sollte vor oder nach dem Aufruf der Funktion im Aufrufer ausgeführt werden:quelle
func
müssen, müssen Sie eine andere Funktion (gemäß Ihrem Muster) herausrechnen. Wenn alle diese isolierten Funktionen dieselben Daten benötigen, kopieren Sie am Ende immer wieder dieselben Stapelargumente oder entscheiden sich, Ihre Argumente auf dem Heap zuzuweisen und einen Zeiger herumzugeben, ohne die grundlegendste Funktion der Sprache zu nutzen ( Funktionsargumente). Kurz gesagt, ich glaube nicht, dass diese Lösung für den schlimmsten Fall skaliert, in dem jeder Zustand überprüft wird, nachdem neue Ressourcen erworben wurden, die bereinigt werden müssen. Beachten Sie jedoch den Kommentar von Nawaz zu Lambdas.func()
und ihrem Destruktor zu erlauben, Ressourcen freizugeben? Wenn etwas außerhalb vonfunc()
Zugriff auf dieselbe Ressource benötigt, sollte es vor dem Aufruffunc()
durch einen geeigneten Ressourcenmanager auf dem Heap deklariert werden .Der Code-Fluss selbst ist bereits ein Code-Geruch, der in der Funktion zu viel passiert. Wenn es dafür keine direkte Lösung gibt (die Funktion ist eine allgemeine Überprüfungsfunktion), ist es möglicherweise besser , RAII zu verwenden, damit Sie zurückkehren können, anstatt zu einem Endabschnitt der Funktion zu springen.
quelle
Wenn Sie während der Ausführung keine lokalen Variablen einführen müssen, können Sie dies häufig reduzieren:
quelle
Ähnlich wie die Antwort von dasblinkenlight, vermeidet jedoch die Zuordnung innerhalb der
if
, die von einem Code-Reviewer " repariert" werden könnte:...
Ich verwende dieses Muster, wenn die Ergebnisse eines Schritts vor dem nächsten Schritt überprüft werden müssen, was sich von einer Situation unterscheidet, in der alle Überprüfungen im Voraus mit einem großen
if( check1() && check2()...
Muster durchgeführt werden könnten .quelle
Verwenden Sie Ausnahmen. Ihr Code sieht viel sauberer aus (und Ausnahmen wurden genau für die Behandlung von Fehlern im Ausführungsablauf eines Programms erstellt). Lesen Sie zum Bereinigen von Ressourcen (Dateideskriptoren, Datenbankverbindungen usw.) den Artikel Warum bietet C ++ kein "finally" -Konstrukt? .
quelle
check()
Bedingung fehlgeschlagen ist, und das ist mit Sicherheit IHRE Annahme, dass das Beispiel keinen Kontext enthält. Unter der Annahme, dass das Programm nicht fortgesetzt werden kann, ist die Verwendung von Ausnahmen der richtige Weg.Für mich
do{...}while(0)
ist in Ordnung. Wenn Sie das nicht sehen wollendo{...}while(0)
, können Sie alternative Schlüsselwörter für sie definieren.Beispiel:
Ich denke, der Compiler wird die unnötige
while(0)
Bedingung in entfernendo{...}while(0)
und die Pausen in einen bedingungslosen Sprung umwandeln. Sie können die Assembler-Version überprüfen, um sicherzugehen.Die Verwendung
goto
erzeugt auch saubereren Code und ist mit der Condition-Then-Jump-Logik unkompliziert. Sie können Folgendes tun:Beachten Sie, dass das Etikett nach dem Schließen platziert wird
}
. Dies ist das Vermeiden eines möglichen Problemsgoto
, da versehentlich ein Code dazwischen platziert wird, weil Sie das Etikett nicht gesehen haben. Es ist jetzt wiedo{...}while(0)
ohne Bedingungscode.Um diesen Code sauberer und verständlicher zu machen, können Sie Folgendes tun:
Mit dieser Option können Sie verschachtelte Blöcke erstellen und angeben, wo Sie beenden / herausspringen möchten.
Spaghetti-Code ist nicht die Schuld des
goto
Programmierers. Sie können weiterhin Spaghetti-Code ohne Verwendung erstellengoto
.quelle
goto
, die Sprachsyntax mit dem Präprozessor millionenfach zu erweitern.Dies ist ein bekanntes und aus funktionaler Programmierperspektive gut gelöstes Problem - die vielleicht Monade.
Als Antwort auf den Kommentar, den ich unten erhalten habe, habe ich meine Einführung hier bearbeitet: Sie finden ausführliche Informationen zur Implementierung von C ++ - Monaden an verschiedenen Stellen, mit denen Sie das erreichen können, was Rotsor vorschlägt. Es dauert eine Weile, um Monaden zu groken. Stattdessen werde ich hier einen schnellen monadenähnlichen Mechanismus für "arme Männer" vorschlagen, für den Sie nur Boost :: optional kennen müssen.
Richten Sie Ihre Berechnungsschritte wie folgt ein:
Jeder Rechenschritt kann offensichtlich so etwas wie eine Rückgabe ausführen,
boost::none
wenn die Option, die er erhalten hat, leer ist. Also zum Beispiel:Dann verkette sie:
Das Schöne daran ist, dass Sie für jeden Rechenschritt klar definierte Komponententests schreiben können. Auch der Aufruf liest sich wie einfaches Englisch (wie es normalerweise beim funktionalen Stil der Fall ist).
Wenn Sie sich nicht für Unveränderlichkeit interessieren und es bequemer ist, jedes Mal dasselbe Objekt zurückzugeben, wenn Sie mit shared_ptr oder ähnlichem eine Variation finden könnten.
quelle
optional<EnabledContext> enabled(Context); optional<EnergisedContext> energised(EnabledContext);
stattdessen die monadische Kompositionsoperation ('bind') anstelle der Funktionsanwendung verwenden.Wie wäre es, wenn Sie die if-Anweisungen in eine zusätzliche Funktion verschieben, um ein numerisches oder enum-Ergebnis zu erhalten?
quelle
So etwas vielleicht
oder Ausnahmen verwenden
Mit Ausnahmen können Sie auch Daten übergeben.
quelle
ever
wird sehr unglücklich sein ...Ich bin nicht besonders in der Art und Weise mit
break
oderreturn
in einem solchen Fall. Angesichts der Tatsache, dass wir normalerweise in einer solchen Situation sind, ist dies normalerweise eine vergleichsweise lange Methode.Wenn wir mehrere Austrittspunkte haben, kann dies zu Schwierigkeiten führen, wenn wir wissen möchten, was dazu führt, dass bestimmte Logik ausgeführt wird: Normalerweise gehen wir einfach weiter nach oben, um diese Logik einzuschließen, und die Kriterien dieser umschließenden Blöcke geben uns Auskunft darüber Lage:
Beispielsweise,
Wenn Sie sich die umschließenden Blöcke ansehen, können Sie leicht herausfinden, dass dies
myLogic()
nur dann geschieht, wennconditionA and conditionB and conditionC
es wahr ist.Es wird viel weniger sichtbar, wenn es frühe Renditen gibt:
Wir können nicht mehr von oben navigieren
myLogic()
nach umschließenden Block betrachten, um den Zustand herauszufinden.Es gibt verschiedene Problemumgehungen, die ich verwendet habe. Hier ist einer von ihnen:
(Natürlich ist es willkommen, dieselbe Variable zu verwenden, um alle zu ersetzen
isA isB isC
.)Ein solcher Ansatz gibt dem Leser zumindest Code, der
myLogic()
ausgeführt wird, wennisB && conditionC
. Der Leser erhält einen Hinweis, dass er weiter nachschlagen muss, was dazu führt, dass isB wahr ist.quelle
Wie wär es damit?
quelle
return condition;
, sonst denke ich, dass dies gut wartbar ist.Ein weiteres Muster, das nützlich ist, wenn Sie je nach Fehlerort unterschiedliche Bereinigungsschritte benötigen:
quelle
Ich bin kein C ++ - Programmierer, daher schreibe ich hier keinen Code, aber bisher hat niemand eine objektorientierte Lösung erwähnt. Also hier ist meine Vermutung dazu:
Verfügen Sie über eine generische Schnittstelle, die eine Methode zur Bewertung einer einzelnen Bedingung bereitstellt. Jetzt können Sie eine Liste von Implementierungen dieser Bedingungen in Ihrem Objekt verwenden, die die betreffende Methode enthalten. Sie durchlaufen die Liste und bewerten jede Bedingung, wobei Sie möglicherweise früh ausbrechen, wenn eine fehlschlägt.
Das Gute ist, dass ein solches Design sehr gut am Open / Closed-Prinzip festhält , da Sie während der Initialisierung des Objekts, das die betreffende Methode enthält, leicht neue Bedingungen hinzufügen können. Sie können der Schnittstelle sogar eine zweite Methode hinzufügen, wobei die Methode zur Bedingungsbewertung eine Beschreibung der Bedingung zurückgibt. Dies kann für selbstdokumentierende Systeme verwendet werden.
Der Nachteil ist jedoch, dass aufgrund der Verwendung von mehr Objekten und der Iteration über die Liste etwas mehr Overhead erforderlich ist.
quelle
So mache ich es.
quelle
Zunächst ein kurzes Beispiel, um zu zeigen, warum dies
goto
keine gute Lösung für C ++ ist:Versuchen Sie, dies in eine Objektdatei zu kompilieren und sehen Sie, was passiert. Versuchen Sie dann das entsprechende
do
+break
+while(0)
.Das war eine Seite. Der Hauptpunkt folgt.
Diese kleinen Stücke von Code erfordern oft einige Art Bereinigung sollte die gesamte Funktion fehlschlagen. Diese Bereinigungen möchten normalerweise in der entgegengesetzten Reihenfolge von den Blöcken selbst erfolgen, wenn Sie die teilweise abgeschlossene Berechnung "abwickeln".
Eine Option, um diese Semantik zu erhalten, ist RAII ; siehe @ utnapistims Antwort. C ++ garantiert, dass automatische Destruktoren in der entgegengesetzten Reihenfolge zu Konstruktoren ausgeführt werden, was natürlich ein "Abwickeln" liefert.
Dafür sind jedoch viele RAII-Klassen erforderlich. Manchmal ist es einfacher, nur den Stapel zu verwenden:
...und so weiter. Dies ist einfach zu prüfen, da der "Rückgängig" -Code neben dem "Do" -Code steht. Einfache Prüfung ist gut. Es macht auch den Kontrollfluss sehr klar. Es ist auch ein nützliches Muster für C.
Es kann erforderlich sein, dass die
calc
Funktionen viele Argumente annehmen, aber das ist normalerweise kein Problem, wenn Ihre Klassen / Strukturen einen guten Zusammenhalt haben. (Das heißt, Dinge, die zusammen gehören, leben in einem einzelnen Objekt, sodass diese Funktionen Zeiger oder Verweise auf eine kleine Anzahl von Objekten aufnehmen und dennoch viele nützliche Arbeiten ausführen können.)quelle
Wenn Ihr Code einen langen Block von if..else if..else-Anweisungen enthält, können Sie versuchen, den gesamten Block mit Hilfe von
Functors
oder neu zu schreibenfunction pointers
. Es ist vielleicht nicht immer die richtige Lösung, aber es ist ziemlich oft.http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.html
quelle
Ich bin erstaunt über die Anzahl der verschiedenen Antworten, die hier präsentiert werden. Aber schließlich habe ich in dem Code, den ich ändern muss (dh diesen
do-while(0)
Hack oder irgendetwas entfernen ), etwas anderes getan als die hier erwähnten Antworten, und ich bin verwirrt, warum niemand dies gedacht hat. Folgendes habe ich getan:Anfangscode:
Jetzt:
Was hier gemacht wurde, ist, dass das Finishing-Zeug in einer Funktion isoliert wurde und die Dinge plötzlich so einfach und sauber geworden sind!
Ich fand diese Lösung erwähnenswert und stellte sie hier zur Verfügung.
quelle
Konsolidieren Sie es in einer
if
Aussage:Dies ist das Muster, das in Sprachen wie Java verwendet wird, in denen das Schlüsselwort goto entfernt wurde.
quelle
true
. Die Kurzschlussauswertung würde garantieren, dass Sie niemals Zwischenprodukte ausführen, für die nicht alle erforderlichen Tests bestanden wurden.(/*do other stuff*/, /*next condition*/)
, dass Sie sie sogar gut formatieren können. Erwarten Sie nur nicht, dass die Leute es mögen. Aber ehrlich gesagt zeigt dies nur, dass es ein Fehler für Java war, diesegoto
AussageWenn Sie für alle Fehler denselben Fehlerbehandler verwenden und jeder Schritt einen Bool zurückgibt, der den Erfolg anzeigt:
(Ähnlich wie bei Tyzoids Antwort, aber Bedingungen sind die Aktionen, und das && verhindert, dass nach dem ersten Fehler zusätzliche Aktionen ausgeführt werden.)
quelle
Warum wurde die Markierungsmethode nicht beantwortet? Sie wird seit Ewigkeiten verwendet.
quelle