Woher kommt der Begriff „nur eine Rückkehr“?

1055

Ich spreche oft mit Programmierern, die sagen: " Setzen Sie nicht mehrere return-Anweisungen in dieselbe Methode ein. " Wenn ich sie auffordern, mir die Gründe dafür zu nennen, bekomme ich nur " Der Codierungsstandard sagt das " oder " Es ist verwirrend ". Wenn sie mir Lösungen mit einer einzelnen return-Anweisung zeigen, erscheint mir der Code hässlicher. Zum Beispiel:

if (condition)
   return 42;
else
   return 97;

" Das ist hässlich, du musst eine lokale Variable verwenden! "

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

Wie wird das Programm durch diesen zu 50% aufgeblähten Code verständlicher? Persönlich finde ich es schwieriger, weil der Zustandsraum gerade um eine andere Variable vergrößert wurde, die leicht hätte verhindert werden können.

Normalerweise würde ich natürlich nur schreiben:

return (condition) ? 42 : 97;

Viele Programmierer meiden jedoch den bedingten Operator und bevorzugen die lange Form.

Woher kommt diese Vorstellung von "nur einer Rückkehr"? Gibt es einen historischen Grund, warum diese Konvention zustande kam?

Überlauf
quelle
2
Dies hängt in gewisser Weise mit dem Refactoring von Schutzklauseln zusammen. stackoverflow.com/a/8493256/679340 Guard-Klausel fügt dem Anfang Ihrer Methoden Renditen hinzu. Und meiner Meinung nach wird Code dadurch viel sauberer.
Piotr Perak
3
Es kam aus dem Begriff der strukturierten Programmierung. Einige argumentieren möglicherweise, dass Sie mit nur einer Rückkehr den Code einfach ändern können, um etwas zu tun, bevor Sie zurückkehren, oder einfach zu debuggen.
Martinkunev
3
Ich denke, das Beispiel ist ein einfacher Fall, in dem ich auf die eine oder andere Weise keine feste Meinung hätte. Das Ideal mit einem Eintrag und einem Ausgang soll uns eher von verrückten Situationen wie 15 return-Anweisungen und zwei weiteren Zweigen, die überhaupt nicht zurückkehren, abbringen!
Mendota
2
Das ist einer der schlimmsten Artikel, die ich je gelesen habe. Es scheint, dass der Autor mehr Zeit damit verbringt, über die Reinheit seines OOP zu phantasieren, als tatsächlich herauszufinden, wie er etwas erreichen kann. Ausdrucks- und Bewertungsbäume haben Wert, aber nicht, wenn Sie stattdessen nur eine normale Funktion schreiben können.
DeadMG
3
Sie sollten die Bedingung insgesamt entfernen. Die Antwort ist 42.
kambunktiver

Antworten:

1119

"Single Entry, Single Exit" wurde geschrieben, als die meisten Programmierungen in Assembler, FORTRAN oder COBOL durchgeführt wurden. Es wurde weitestgehend falsch interpretiert, da moderne Sprachen die Praktiken, vor denen Dijkstra warnte, nicht unterstützen.

"Einzelner Eintrag" bedeutet "keine alternativen Einstiegspunkte für Funktionen erstellen". In der Assemblersprache ist es natürlich möglich, an jeder Anweisung eine Funktion einzugeben. FORTRAN unterstützte mehrere Einträge zu Funktionen mit der ENTRYAnweisung:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

"Single Exit" bedeutete, dass eine Funktion nur an eine Stelle zurückkehren sollte: die Anweisung, die unmittelbar auf den Aufruf folgt. Es bedeutete nicht , dass eine Funktion nur von einer Stelle zurückkehren sollte. Beim Schreiben von Structured Programming war es üblich, dass eine Funktion einen Fehler anzeigt, indem sie an einen anderen Ort zurückkehrt. FORTRAN hat dies über "Alternate Return" unterstützt:

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

Beide Techniken waren sehr fehleranfällig. Die Verwendung von alternativen Einträgen ließ häufig eine Variable uninitialisiert. Die Verwendung alternativer Rückgaben hatte alle Probleme einer GOTO-Anweisung mit der zusätzlichen Komplikation, dass die Verzweigungsbedingung nicht an die Verzweigung angrenzte, sondern sich irgendwo in der Unterroutine befand.

Kevin Cline
quelle
38
Und vergessen Sie nicht den Spaghetti-Code . Es war nicht unbekannt, dass Subroutinen mit einem GOTO anstelle einer Rückgabe beendet wurden, wobei die Funktionsaufrufparameter und die Rückgabeadresse auf dem Stapel belassen wurden. Ein einzelner Exit wurde gefördert, um mindestens alle Codepfade zu einer RETURN-Anweisung zu leiten.
TMN
2
@TMN: Früher hatten die meisten Maschinen keinen Hardware-Stack. Rekursion wurde im Allgemeinen nicht unterstützt. Unterprogrammargumente und Rücksprungadresse wurden an festen Stellen neben dem Unterprogrammcode gespeichert. Die Rückkehr war nur ein indirekter Schritt.
Kevin Cline
5
@kevin: Ja, aber laut dir bedeutet das nicht einmal mehr, als was es erfunden wurde. (Übrigens, ich bin mir ziemlich sicher, dass Fred gefragt hat, woher die aktuelle Interpretation von "Single Exit" stammt.) Auch C hatte constseitdem, bevor viele der Benutzer hier geboren wurden, keine Notwendigkeit mehr für Kapitalkonstanten Aber Java hat all diese schlechten alten C-Gewohnheiten bewahrt .
sbi
3
Verstoßen Ausnahmen also gegen diese Interpretation von Single Exit? (Oder ihr primitiverer Cousin setjmp/longjmp?)
Mason Wheeler
2
Auch wenn das Op nach der aktuellen Interpretation von Single Return gefragt hat, ist diese Antwort die mit den historischsten Wurzeln. Es hat keinen Sinn, in der Regel eine einzelne Rückgabe zu verwenden , es sei denn, Sie möchten, dass Ihre Sprache der Attraktivität von VB (nicht .NET) entspricht. Denken Sie daran, auch die boolesche Logik ohne Kurzschluss zu verwenden.
29.
912

Dieser Begriff von Single Entry, Single Exit (SESE) stammt aus Sprachen mit expliziter Ressourcenverwaltung wie C und Assembly. In C verliert Code wie dieser Ressourcen:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

In solchen Sprachen haben Sie grundsätzlich drei Möglichkeiten:

  • Replizieren Sie den Bereinigungscode.
    Pfui. Redundanz ist immer schlecht.

  • Verwenden Sie a goto, um zum Bereinigungscode zu springen.
    Dies erfordert, dass der Bereinigungscode das Letzte in der Funktion ist. (Und deshalb argumentieren einige, dass gotoes seinen Platz hat. Und es hat in der Tat - in C.)

  • Führen Sie eine lokale Variable ein und manipulieren Sie den Steuerungsfluss.
    Der Nachteil ist , dass Steuerfluss durch Syntax manipuliert (denken break, return, if, while) ist viel einfacher als durch den Zustand der Variablen manipulierte Steuerfluss zu folgen (da diese Variablen haben keinen Zustand , wenn Sie auf dem Algorithmus aussehen).

In der Montage ist es noch seltsamer, weil Sie zu jeder Adresse in einer Funktion springen können, wenn Sie diese Funktion aufrufen, was praktisch bedeutet, dass Sie eine nahezu unbegrenzte Anzahl von Eintrittspunkten für jede Funktion haben. (Manchmal ist dies hilfreich. Solche Thunks sind eine übliche Technik für Compiler, um die thisZeigeranpassung zu implementieren, die zum Aufrufen von virtualFunktionen in Mehrfachvererbungsszenarios in C ++ erforderlich ist .)

Wenn Sie Ressourcen manuell verwalten müssen, führt das Ausnutzen der Optionen zum Aufrufen oder Verlassen einer Funktion zu komplexerem Code und damit zu Fehlern. Daher erschien eine Denkschule, die SESE propagierte, um saubereren Code und weniger Bugs zu erhalten.


Wenn jedoch eine Sprache Ausnahmen aufweist, kann (fast) jede Funktion zu (fast) jedem Zeitpunkt vorzeitig beendet werden. Daher müssen Sie ohnehin Vorkehrungen für eine vorzeitige Rückkehr treffen. (Ich denke, dies finallywird hauptsächlich in Java und using(bei der Implementierung IDisposable, finallyansonsten) in C # verwendet; C ++ verwendet stattdessen RAII .) Wenn Sie dies getan haben, können Sie es nicht versäumen, aufgrund einer frühen returnAussage nach sich aufzuräumen das stärkste Argument für SESE ist verschwunden.

Damit bleibt die Lesbarkeit. Natürlich ist eine 200-LoC-Funktion mit einem halben Dutzend returnzufällig darüber gestreuten Anweisungen kein guter Programmierstil und ergibt keinen lesbaren Code. Aber eine solche Funktion wäre auch ohne diese vorzeitigen Erträge nicht leicht zu verstehen.

In Sprachen, in denen Ressourcen nicht manuell verwaltet werden oder verwaltet werden sollten, ist die Einhaltung der alten SESE-Konvention wenig oder gar nicht sinnvoll. OTOH, wie ich oben dargelegt habe, macht SESE Code oft komplexer . Es ist ein Dinosaurier, der (mit Ausnahme von C) nicht gut in die meisten heutigen Sprachen passt. Anstatt die Verständlichkeit des Codes zu verbessern, behindert es ihn.


Warum halten Java-Programmierer daran fest? Ich weiß es nicht, aber von meinem (externen) POV aus hat Java eine Menge Konventionen von C übernommen (wo sie Sinn machen) und sie auf seine OO-Welt angewendet (wo sie nutzlos oder ausgesprochen schlecht sind), wo sie sich jetzt festhalten sie, egal was die Kosten. (Wie die Konvention, alle Variablen am Anfang des Gültigkeitsbereichs zu definieren.)

Programmierer halten aus irrationalen Gründen an allerlei merkwürdigen Notationen fest. (Tief verschachtelte strukturelle Aussagen - "Pfeilspitzen" - wurden in Sprachen wie Pascal einst als schöner Code angesehen.) Eine logische Argumentation scheint die Mehrheit von ihnen nicht davon zu überzeugen, von ihren etablierten Methoden abzuweichen. Der beste Weg, solche Gewohnheiten zu ändern, ist wahrscheinlich, sie früh zu lehren, das Beste und nicht das Konventionelle zu tun. Als Programmierlehrer haben Sie es in der Hand.:)

sbi
quelle
52
Richtig. In Java gehört Bereinigungscode in finallyKlauseln, in denen er unabhängig von frühen returns oder Ausnahmen ausgeführt wird.
Dan04
15
@ dan04 In Java 7 brauchst du nicht einmal die finallymeiste Zeit.
R. Martinho Fernandes
93
@Steven: Das kannst du natürlich demonstrieren! Tatsächlich können Sie verschlungenen und komplexen Code mit jeder Funktion anzeigen, die auch angezeigt werden kann, um den Code einfacher und verständlicher zu machen. Alles kann missbraucht werden. Es geht darum, Code so zu schreiben, dass er leichter zu verstehen ist , und wenn es darum geht, SESE aus dem Fenster zu werfen, sollten Sie es so machen und die alten Gewohnheiten, die für verschiedene Sprachen galten, verdammt noch mal. Aber ich würde auch nicht zögern, die Ausführung durch Variablen zu steuern, wenn ich der Meinung wäre, dass dies das Lesen des Codes erleichtert. Ich kann mich nur nicht erinnern, in fast zwei Jahrzehnten einen solchen Code gesehen zu haben.
sbi
21
@Karl: In der Tat ist es ein schwerwiegender Mangel von GC-Sprachen wie Java, dass Sie nicht eine Ressource bereinigen müssen, sondern mit allen anderen fehlschlagen. (C ++ löst dieses Problem für alle Ressourcen RAH ) . Aber ich rede nicht einmal von Speicher (ich nur setzen malloc()und free()in einen Kommentar als Beispiel), ich spreche über Ressourcen im Allgemeinen. Ich habe auch nicht impliziert, dass GC diese Probleme lösen würde. (Ich habe C ++ erwähnt, das GC nicht sofort einsatzbereit hat.) Soweit ich weiß, wird Java finallyzur Lösung dieses Problems verwendet.
sbi
10
@sbi: Wichtiger für eine Funktion (Prozedur, Methode usw.) als nur eine Seite lang zu sein, ist, dass die Funktion einen klar definierten Vertrag hat. Wenn etwas nicht klar ist, weil es zerhackt wurde, um eine willkürliche Längenbeschränkung zu erfüllen, ist das schlecht. Beim Programmieren geht es darum, verschiedene, manchmal widersprüchliche Kräfte gegeneinander auszuspielen.
Donal Fellows
81

Einerseits erleichtern einzelne return-Anweisungen die Protokollierung sowie Formen des Debuggens, die auf der Protokollierung beruhen. Ich erinnere mich noch gut daran, wie oft ich die Funktion auf Einzelrückgabe reduzieren musste, um den Rückgabewert an einer Stelle auszudrucken.

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

Auf der anderen Seite können Sie dies in function()die Aufrufe umgestalten _function()und das Ergebnis protokollieren.

perreal
quelle
31
Ich möchte auch hinzufügen, dass dies das Debuggen erleichtert, da Sie immer nur einen Haltepunkt festlegen müssen, um alle Exits * aus der Funktion abzufangen. Ich glaube, dass Sie mit einigen IDEs einen Haltepunkt in der Klammer der Funktion setzen können, um dasselbe zu tun. (* sofern Sie nicht exit anrufen)
Skizz 10.11.11
3
Aus einem ähnlichen Grund ist es auch einfacher, die Funktion zu erweitern (zu ergänzen), da Ihre neue Funktionalität nicht vor jeder Rückkehr eingefügt werden muss. Angenommen, Sie müssen ein Protokoll mit dem Ergebnis des Funktionsaufrufs aktualisieren.
JeffSahol
63
Um ehrlich zu sein, wenn ich diesen Code beibehalten würde, hätte ich lieber einen vernünftig definierten Code _function()mit returns an geeigneten Stellen und einem Wrapper function(), der die externe Protokollierung übernimmt, als einen Code function()mit verzerrter Logik, damit alle Rückgaben in einen einzelnen Exit passen -point, damit ich eine zusätzliche Anweisung vor diesem Punkt einfügen kann.
Ruakh
11
In einigen Debuggern (MSVS) können Sie einen Haltepunkt für die letzte schließende Klammer setzen
Abyx
6
Drucken! = Debuggen. Das ist überhaupt kein Argument.
Piotr Perak
53

"Single Entry, Single Exit" entstand mit der Revolution der strukturierten Programmierung in den frühen 1970er Jahren, die durch den Brief von Edsger W. Dijkstra an den Herausgeber " GOTO Statement Considered Harmful " eingeleitet wurde . Die Konzepte für die strukturierte Programmierung wurden im klassischen Buch "Structured Programming" von Ole Johan-Dahl, Edsger W. Dijkstra und Charles Anthony Richard Hoare ausführlich beschrieben.

"GOTO Statement als schädlich" muss auch heute noch gelesen werden. "Structured Programming" ist veraltet, aber immer noch sehr, sehr lohnend und sollte ganz oben auf der "Must Read" -Liste eines Entwicklers stehen, weit über allem von Steve McConnell. (In Dahls Abschnitt werden die Grundlagen der Klassen in Simula 67 erläutert, die die technische Grundlage für Klassen in C ++ und die gesamte objektorientierte Programmierung bilden.)

John R. Strohm
quelle
6
Der Artikel wurde in Tagen vor C geschrieben, als GOTOs stark genutzt wurden. Sie sind nicht der Feind, aber diese Antwort ist definitiv richtig. Eine return-Anweisung, die nicht am Ende einer Funktion steht, ist praktisch ein goto.
user606723
31
Der Artikel wurde auch in den Tagen geschrieben, in denen gotoman buchstäblich überall hingehen konnte , wie an einem zufälligen Punkt in einer anderen Funktion, ohne die Vorstellung von Prozeduren, Funktionen, einem Aufrufstapel usw. Keine vernünftige Sprache erlaubt dies heutzutage mit einem Straight goto. Cs setjmp/ longjmpist der einzige mir bekannte Ausnahmefall, und selbst das erfordert Kooperation von beiden Seiten. (Halb-ironisch, dass ich dort das Wort "außergewöhnlich" verwendet habe, wenn man bedenkt, dass Ausnahmen fast dasselbe tun ...) Im Grunde rät der Artikel von einer Praxis ab, die schon lange tot ist.
CHAO
5
Aus dem letzten Absatz von "Goto Statement als schädlich": "in [2] scheint Guiseppe Jacopini die (logische) Überflüssigkeit der go to-Anweisung bewiesen zu haben. Die Übung, ein beliebiges Flussdiagramm mehr oder weniger mechanisch in eine Sprung- weniger ist jedoch nicht zu empfehlen . Dann kann nicht erwartet werden, dass das resultierende Flussdiagramm transparenter ist als das ursprüngliche. "
hugomg
10
Was hat das mit der Frage zu tun? Ja, Dijkstras Arbeit führte schließlich zu SESE-Sprachen, und was nun? Babbages Arbeit auch. Und vielleicht sollten Sie das Papier noch einmal lesen, wenn Sie der Meinung sind, dass es etwas über mehrere Austrittspunkte in einer Funktion aussagt . Weil es nicht so ist.
9.
10
@ John, Sie scheinen zu versuchen, die Frage zu beantworten, ohne sie tatsächlich zu beantworten. Es ist eine gute Leseliste, aber Sie haben nichts zitiert oder umschrieben, um Ihre Behauptung zu rechtfertigen, dass dieser Aufsatz und dieses Buch etwas über die Besorgnis des Fragestellers zu sagen haben. In der Tat haben Sie, abgesehen von Kommentaren, nichts Wesentliches zu der Frage gesagt . Erwägen Sie, diese Antwort zu erweitern.
Shog9
35

Es ist immer einfach, Fowler zu verlinken.

Eines der wichtigsten Beispiele, die gegen SESE sprechen, sind Schutzklauseln:

Ersetzen Sie verschachtelte Bedingungen durch Schutzklauseln

Verwenden Sie für alle Sonderfälle Schutzklauseln

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

                                                                                                         http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

Weitere Informationen finden Sie auf Seite 250 unter Refactoring ...

Pieter B
quelle
11
Ein weiteres schlechtes Beispiel: Es könnte genauso gut mit else-ifs behoben werden.
Jack
1
Ihr Beispiel ist nicht fair, wie wäre es damit: double getPayAmount () {double ret = normalPayAmount (); if (_isDead) ret = deadAmount (); if (_isSeparated) ret = disconnectedAmount (); if (_isRetired) ret = retiredAmount (); return ret; };
Charbel
6
@Charbel Das ist nicht dasselbe. Wenn _isSeparatedund _isRetiredkönnen beide wahr sein (und warum wäre das nicht möglich?), Geben Sie den falschen Betrag zurück.
HDV
2
@Konchog „ verschachtelte Bedingungen werden eine bessere Ausführungszeit als Wach Klauseln bieten “ Diese majorly braucht ein Zitat. Ich habe meine Zweifel, dass es überhaupt zuverlässig stimmt. Wie unterscheiden sich in diesem Fall beispielsweise frühe Rückgaben von logischen Kurzschlüssen in Bezug auf den generierten Code? Auch wenn es darauf ankam, kann ich mir keinen Fall vorstellen, in dem der Unterschied mehr als ein infinitesimaler Faserband wäre. Sie führen also eine vorzeitige Optimierung durch, indem Sie den Code weniger lesbar machen, nur um einen unbewiesenen theoretischen Punkt zu erfüllen, der Ihrer Meinung nach zu einem etwas schnelleren Code führt. Das machen wir hier nicht
underscore_d
1
@underscore_d, du hast recht. Das hängt stark vom Compiler ab, kann aber mehr Platz beanspruchen. Schauen Sie sich die beiden Pseudo-Assemblys an und Sie können leicht erkennen, warum Schutzklauseln aus höheren Sprachen stammen. "A" -Test (1); branch_fail end; Test (2); branch_fail end; Test (3); branch_fail end; {CODE} end: return; "B" -Test (1); branch_good next1; Rückkehr; next1: test (2); branch_good next2; Rückkehr; next2: test (3); branch_good next3; Rückkehr; next3: {CODE} return;
Konchog
11

Ich habe vor einiger Zeit einen Blog-Beitrag zu diesem Thema geschrieben.

Die Quintessenz ist, dass diese Regel aus dem Zeitalter von Sprachen stammt, die keine Speicherbereinigung oder Ausnahmebehandlung haben. Es gibt keine offizielle Studie, die belegt, dass diese Regel zu einem besseren Code in modernen Sprachen führt. Sie können es jederzeit ignorieren, wenn dies zu einem kürzeren oder besser lesbaren Code führt. Die Java-Leute, die darauf bestehen, folgen blind und fraglos einer veralteten, sinnlosen Regel.

Diese Frage wurde auch bei Stackoverflow gestellt

Anthony
quelle
Hey, ich kann diesen Link nicht mehr erreichen. Haben Sie zufällig eine Version, die irgendwo noch verfügbar ist?
Nic Hartley
Hi, QPT, guter Ort. Ich habe den Blogbeitrag zurückgebracht und die URL oben aktualisiert. Es sollte jetzt verlinken!
Anthony
Es steckt aber noch mehr dahinter. Mit SESE ist es viel einfacher, den genauen Ausführungszeitpunkt zu verwalten. Verschachtelte Bedingungen können oft ohnehin mit einem Switch überarbeitet werden. Es geht nicht nur darum, ob es einen Rückgabewert gibt oder nicht.
Konchog
Wenn Sie behaupten, dass es keine formelle Studie gibt, die dies unterstützt, sollten Sie eine Verknüpfung zu einer solchen herstellen, die dagegen spricht.
Mehrdad
Mehrdad, wenn es eine formelle Studie gibt, die dies unterstützt, zeige es. Das ist alles. Das Beharren auf Beweisen dagegen verlagert die Beweislast.
Anthony
7

Eine Rückgabe erleichtert das Refactoring. Versuchen Sie, eine "Extraktionsmethode" für den inneren Körper einer for-Schleife auszuführen, die ein return, break oder continue enthält. Dies schlägt fehl, da Sie Ihren Kontrollfluss unterbrochen haben.

Der Punkt ist: Ich denke, niemand gibt vor, perfekten Code zu schreiben. Daher wird Code regelmäßig überarbeitet, um "verbessert" und erweitert zu werden. Mein Ziel wäre es also, meinen Code so umgestaltungsfreundlich wie möglich zu halten.

Oft stehe ich vor dem Problem, dass ich Funktionen komplett neu formulieren muss, wenn sie Kontrollflussunterbrecher enthalten und ich nur wenig Funktionalität hinzufügen möchte. Dies ist sehr fehleranfällig, da Sie den Steuerungsfluss vollständig ändern, anstatt neue Pfade zu isolierten Sammelformen einzufügen. Wenn Sie am Ende nur eine einzige Rückkehr haben oder Wachen zum Verlassen einer Schleife verwenden, haben Sie natürlich mehr Verschachtelung und mehr Code. Sie erhalten jedoch Compiler- und IDE-unterstützte Refactoring-Funktionen.

oopexpert
quelle
Gleiches gilt für Variablen. Welches sind die Alternative zu Kontrollflusskonstrukten wie Early Return?
Deduplizierer
Variablen werden Sie in der Regel nicht daran hindern, Ihren Code so in Teile zu zerlegen, dass der vorhandene Kontrollfluss erhalten bleibt. Versuchen Sie "Methode extrahieren". Die IDEs sind nur in der Lage, Refactorings zur Kontrolle des Ablaufs durchzuführen, da sie keine Semantik von dem ableiten können, was Sie geschrieben haben.
oopexpert
5

Berücksichtigen Sie die Tatsache, dass mehrere return-Anweisungen den GOTOs einer einzelnen return-Anweisung entsprechen. Dies ist auch bei break-Anweisungen der Fall. Daher betrachten manche, wie ich, sie als GOTOs in jeder Hinsicht.

Ich halte diese Arten von GOTOs jedoch nicht für schädlich und zögere nicht, ein tatsächliches GOTO in meinem Code zu verwenden, wenn ich einen guten Grund dafür finde.

Meine allgemeine Regel ist, dass GOTOs nur zur Flusskontrolle dienen. Sie sollten niemals für Schleifen verwendet werden, und Sie sollten niemals "aufwärts" oder "rückwärts" gehen. (So ​​funktionieren Pausen / Retouren)

Wie andere erwähnt haben, ist das Folgende ein Muss GOTO-Erklärung als schädlich zu lesen. Beachten Sie
jedoch, dass dies im Jahr 1970 geschrieben wurde, als GOTOs viel zu stark genutzt wurden. Nicht jedes GOTO ist schädlich und ich würde von ihrer Verwendung nicht abraten, solange Sie sie nicht anstelle normaler Konstrukte verwenden, sondern in dem merkwürdigen Fall, dass die Verwendung normaler Konstrukte äußerst unpraktisch wäre.

Ich finde, dass die Verwendung in Fehlerfällen, in denen Sie einen Bereich aufgrund eines Fehlers verlassen müssen, der im Normalfall nie auftreten sollte, manchmal nützlich ist. Sie sollten aber auch in Betracht ziehen, diesen Code in eine separate Funktion zu integrieren, damit Sie einfach früher zurückkehren können, anstatt ein GOTO zu verwenden ... aber manchmal ist das auch unpraktisch.

user606723
quelle
6
Alle strukturierten Konstrukte, die gotos ersetzen, werden in Bezug auf goto implementiert. ZB Schleifen, "if" und "case". Das macht sie nicht schlecht - im Gegenteil. Auch ist es "Absichten und Zwecke".
Anthony
Touche, aber das unterscheidet meinen Standpunkt nicht ... Es macht meine Erklärung nur ein wenig falsch. Naja.
user606723
GOTO sollte immer in Ordnung sein, solange (1) sich das Ziel in derselben Methode oder Funktion befindet und (2) die Richtung im Code vorwärts ist (Code überspringen) und (3) sich das Ziel nicht in einer anderen verschachtelten Struktur befindet (z. B. GOTO von der Mitte des Falls zur Mitte des Sonstfalls). Wenn Sie diese Regeln einhalten, riechen alle Missbräuche von GOTO sowohl optisch als auch logisch sehr stark nach Code.
Mikko Rantalainen
3

Zyklomatische Komplexität

Ich habe gesehen, dass SonarCube mehrere return-Anweisungen verwendet, um die zyklomatische Komplexität zu bestimmen. Je mehr die return-Anweisungen, desto höher die zyklomatische Komplexität

Rückgabetyp ändern

Mehrere Rückgaben bedeuten, dass wir an mehreren Stellen in der Funktion Änderungen vornehmen müssen, wenn wir uns entscheiden, unseren Rückgabetyp zu ändern.

Multiple Exit

Das Debuggen ist schwieriger, da die Logik in Verbindung mit den bedingten Anweisungen sorgfältig untersucht werden muss, um zu verstehen, was den zurückgegebenen Wert verursacht hat.

Refactored Lösung

Die Lösung für mehrere return-Anweisungen besteht darin, sie durch polymorphe Anweisungen zu ersetzen, nachdem das erforderliche Implementierungsobjekt aufgelöst wurde.

Sorter
quelle
3
Durch die Umstellung von mehreren Rückgaben auf das Festlegen des Rückgabewerts an mehreren Stellen wird die zyklomatische Komplexität nicht beseitigt, sondern nur die Austrittsstelle vereinheitlicht. Alle Probleme, auf die die zyklomatische Komplexität im gegebenen Kontext hinweisen kann, bleiben bestehen. "Das Debuggen ist schwieriger, da die Logik in Verbindung mit den bedingten Anweisungen sorgfältig untersucht werden muss, um zu verstehen, was den zurückgegebenen Wert verursacht hat." Auch hier ändert sich die Logik nicht, indem die Rückgabe vereinheitlicht wird. Wenn Sie Code sorgfältig studieren müssen, um zu verstehen, wie er funktioniert, müssen Sie ihn komplett überarbeiten.
WillD