Sollte eine Funktion nur eine return-Anweisung haben?

780

Gibt es gute Gründe, warum es besser ist, nur eine return-Anweisung in einer Funktion zu haben?

Oder ist es in Ordnung, von einer Funktion zurückzukehren, sobald dies logisch korrekt ist, was bedeutet, dass die Funktion möglicherweise viele return-Anweisungen enthält?

Tim Post
quelle
25
Ich stimme nicht zu, dass die Frage sprachunabhängig ist. Bei einigen Sprachen ist es natürlicher und bequemer, mehrere Rückgaben zu haben als bei anderen. Ich würde mich eher über frühe Rückgaben in einer C-Funktion beschweren als in einer C ++ - Funktion, die RAII verwendet.
Adrian McCarthy
3
Dies ist eng verwandt und hat ausgezeichnete Antworten: programmers.stackexchange.com/questions/118703/…
Tim Schmelter
sprachunabhängig? Erklären Sie jemandem, der eine funktionale Sprache verwendet, dass er eine Rückgabe pro Funktion verwenden muss: p
Boiethios

Antworten:

741

Ich habe oft mehrere Anweisungen am Anfang einer Methode, um für "einfache" Situationen zurückzukehren. Zum Beispiel:

public void DoStuff(Foo foo)
{
    if (foo != null)
    {
        ...
    }
}

... kann so besser lesbar gemacht werden (IMHO):

public void DoStuff(Foo foo)
{
    if (foo == null) return;

    ...
}

Also ja, ich denke es ist in Ordnung, mehrere "Austrittspunkte" von einer Funktion / Methode zu haben.

Matt Hamilton
quelle
83
Einverstanden. Obwohl es unmöglich sein kann, mehrere Austrittspunkte zu haben, denke ich definitiv, dass es besser ist, als Ihre gesamte Funktion in einen IF-Block zu setzen. Verwenden Sie return so oft es sinnvoll ist, um Ihren Code lesbar zu halten.
Joshua Carmody
172
Dies ist als "Guard Statement" bekannt und ist Fowlers Refactoring.
Lars Westergren
12
Wenn Funktionen relativ kurz gehalten werden, ist es nicht schwierig, der Struktur einer Funktion mit einem Rückgabepunkt nahe der Mitte zu folgen.
KJAWolf
21
Riesiger Block von if-else-Anweisungen, jede mit einer Rückgabe? Das ist nichts. So etwas lässt sich normalerweise leicht umgestalten. (Zumindest ist die häufigere Single-Exit-Variante mit einer Ergebnisvariablen die, daher bezweifle ich, dass die Multi-Exit-Variante schwieriger wäre.) Wenn Sie echte Kopfschmerzen haben möchten, schauen Sie sich eine Kombination aus if-else- und while-Schleifen an (gesteuert durch local) Boolesche Werte), bei denen Ergebnisvariablen festgelegt werden, die zu einem endgültigen Austrittspunkt am Ende der Methode führen. Das ist die Single-Exit-Idee, die verrückt geworden ist, und ja, ich spreche aus der tatsächlichen Erfahrung, die damit zu tun hatte.
Marcus Andrén
7
'Stellen Sie sich vor: Sie müssen die "IncreaseStuffCallCounter" -Methode am Ende der' DoStuff'-Funktion ausführen. Was werden Sie in diesem Fall tun? :) '-DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Jim Balter
355

Niemand hat Code Complete erwähnt oder zitiert, also werde ich es tun.

17.1 Rückkehr

Minimieren Sie die Anzahl der Rückgaben in jeder Routine . Es ist schwieriger, eine Routine zu verstehen, wenn Sie beim Lesen unten nicht wissen, dass sie irgendwo darüber zurückkehrt.

Verwenden Sie eine Rückgabe, wenn dies die Lesbarkeit verbessert . In bestimmten Routinen möchten Sie die Antwort, sobald Sie sie kennen, sofort an die aufrufende Routine zurückgeben. Wenn die Routine so definiert ist, dass keine Bereinigung erforderlich ist, bedeutet eine nicht sofortige Rückgabe, dass Sie mehr Code schreiben müssen.

Chris S.
quelle
64
+1 für die Nuance "Minimieren", aber nicht mehrfache Rückgabe verbieten.
Raedwald
13
"schwerer zu verstehen" ist sehr subjektiv, insbesondere wenn der Autor keine empirischen Beweise zur Stützung der allgemeinen Behauptung vorlegt ... eine einzige Variable, die an vielen bedingten Stellen im Code für die endgültige Rückgabeanweisung festgelegt werden muss, wird gleichermaßen unterworfen zu "nicht bewusst, dass die Variable irgendwo über der Funktion zugewiesen wurde!
Heston T. Holtmann
26
Persönlich bevorzuge ich eine frühzeitige Rückkehr, wo dies möglich ist. Warum? Wenn Sie das Schlüsselwort return für einen bestimmten Fall sehen, wissen Sie sofort "Ich bin fertig" - Sie müssen nicht weiterlesen, um herauszufinden, was, wenn überhaupt, danach passiert.
Mark Simpson
12
@ HestonT.Holtmann: Die Sache , die gemacht Code Complete unter Programmierung Bücher einzigartig war , dass die Beratung ist durch empirische Beweise gestützt.
Adrian McCarthy
9
Dies sollte wahrscheinlich die akzeptierte Antwort sein, da erwähnt wird, dass mehrere Rückgabepunkte nicht immer gut, aber manchmal notwendig sind.
Rafid
229

Ich würde sagen, es wäre unglaublich unklug, sich willkürlich gegen mehrere Austrittspunkte zu entscheiden, da ich festgestellt habe, dass die Technik in der Praxis immer wieder nützlich ist. Tatsächlich habe ich vorhandenen Code aus Gründen der Klarheit häufig auf mehrere Austrittspunkte umgestaltet . Wir können die beiden Ansätze folgendermaßen vergleichen:

string fooBar(string s, int? i) {
  string ret = "";
  if(!string.IsNullOrEmpty(s) && i != null) {
    var res = someFunction(s, i);

    bool passed = true;
    foreach(var r in res) {
      if(!r.Passed) {
        passed = false;
        break;
      }
    }

    if(passed) {
      // Rest of code...
    }
  }

  return ret;
}

Vergleichen Sie dies mit dem Code , wo mehrere Austrittspunkte sind zulässig: -

string fooBar(string s, int? i) {
  var ret = "";
  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Ich denke, letzteres ist wesentlich klarer. Soweit ich das beurteilen kann, ist die Kritik an mehreren Austrittspunkten heutzutage eine eher archaische Sichtweise.

ljs
quelle
12
Klarheit liegt im Auge des Betrachters - ich betrachte eine Funktion und suche einen Anfang, eine Mitte und ein Ende. Wenn die Funktion klein ist, ist sie in Ordnung - aber wenn Sie versuchen herauszufinden, warum etwas kaputt ist und "Rest of Code" sich als nicht trivial herausstellt, können Sie ewig danach suchen, warum ret ist
Murph
7
Zuallererst ist dies ein erfundenes Beispiel. Zweitens: Woher kommt: string ret; "go in the second version? Drittens enthält Ret keine nützlichen Informationen. Viertens, warum so viel Logik in einer Funktion / Methode? Fünftens, warum nicht DidValuesPass (Typ res) und dann RestOfCode () trennen Unterfunktionen?
Rick Minerich
25
@Rick 1. Nach meiner Erfahrung nicht erfunden, ist dies tatsächlich ein Muster, auf das ich schon oft gestoßen bin. 2. Es ist im 'Rest des Codes' zugewiesen, vielleicht ist das nicht klar. 3. Ähm? Es ist ein Beispiel? 4. Nun, ich denke, es ist in dieser Hinsicht
erfunden
5
@Rick der Punkt ist, dass es oft einfacher ist, früh zurückzukehren, als Code in eine riesige if-Anweisung zu packen. Es kommt meiner Erfahrung nach viel vor, selbst bei richtigem Refactoring.
ljs
5
Der Punkt, dass nicht mehrere return-Anweisungen vorhanden sind, erleichtert das Debuggen, eine weitaus zweite Sekunde dient der Lesbarkeit. Ein Haltepunkt am Ende ermöglicht es Ihnen, den Exit-Wert ohne Ausnahmen anzuzeigen. In Bezug auf die Lesbarkeit verhalten sich die beiden gezeigten Funktionen nicht gleich. Die Standardrückgabe ist eine leere Zeichenfolge, wenn! R.Passed, aber die "besser lesbare" ändert dies, um null zurückzugeben. Der Autor hat falsch verstanden, dass es früher schon nach wenigen Zeilen einen Standard gab. Selbst in trivialen Beispielen ist es leicht, unklare Standardrückgaben zu haben, was durch eine einzige Rückgabe am Ende erzwungen werden kann.
Mitch
191

Ich arbeite derzeit an einer Codebasis, in der zwei der Leute, die daran arbeiten, blind die "Single Point of Exit" -Theorie unterschreiben, und ich kann Ihnen sagen, dass es aus Erfahrung eine schrecklich schreckliche Praxis ist. Die Pflege von Code ist äußerst schwierig, und ich zeige Ihnen, warum.

Mit der "Single Point of Exit" -Theorie erhalten Sie unweigerlich Code, der so aussieht:

function()
{
    HRESULT error = S_OK;

    if(SUCCEEDED(Operation1()))
    {
        if(SUCCEEDED(Operation2()))
        {
            if(SUCCEEDED(Operation3()))
            {
                if(SUCCEEDED(Operation4()))
                {
                }
                else
                {
                    error = OPERATION4FAILED;
                }
            }
            else
            {
                error = OPERATION3FAILED;
            }
        }
        else
        {
            error = OPERATION2FAILED;
        }
    }
    else
    {
        error = OPERATION1FAILED;
    }

    return error;
}

Dies macht es nicht nur sehr schwierig, dem Code zu folgen, sondern sagt auch später, dass Sie zurückgehen und eine Operation zwischen 1 und 2 hinzufügen müssen. Sie müssen fast die gesamte Freaking-Funktion einrücken und viel Glück, um sicherzustellen, dass alles funktioniert Ihre if / else-Bedingungen und Klammern sind richtig aufeinander abgestimmt.

Diese Methode macht die Code-Wartung extrem schwierig und fehleranfällig.

17 von 26
quelle
5
@Murph: Sie können nicht wissen, dass nach jeder Bedingung nichts anderes passiert, ohne jede durchzulesen. Normalerweise würde ich sagen, dass diese Art von Themen subjektiv sind, aber das ist einfach eindeutig falsch. Mit einer Rückkehr zu jedem Fehler sind Sie fertig und wissen genau, was passiert ist.
GEOCHET
6
@ Murphy: Ich habe gesehen, wie diese Art von Code verwendet, missbraucht und überbeansprucht wurde. Das Beispiel ist recht einfach, da es kein wahres if / else im Inneren gibt. Alles, was diese Art von Code benötigt, um zusammenzubrechen, ist ein "anderes", das vergessen wurde. AFAIK, dieser Code braucht dringend Ausnahmen.
Paercebal
15
Sie können es in dieses umgestalten, behält seine "Reinheit": if (! SUCCEEDED (Operation1 ())) {} else error = OPERATION1FAILED; if (error! = S_OK) {if (SUCCEEDED (Operation2 ())) {} else error = OPERATION2FAILED; } if (error! = S_OK) {if (SUCCEEDED (Operation3 ())) {} else error = OPERATION3FAILED; } //usw.
Joe Pineda
6
Dieser Code hat nicht nur einen Exit-Punkt: Jede dieser "error =" - Anweisungen befindet sich auf einem Exit-Pfad. Es geht nicht nur darum, Funktionen zu verlassen, sondern einen Block oder eine Sequenz zu verlassen.
Jay Bazuzi
6
Ich bin nicht der Meinung, dass eine einzelne Rückkehr "unvermeidlich" zu einer tiefen Verschachtelung führt. Ihr Beispiel kann als einfache lineare Funktion mit einer einzigen Rückgabe (ohne gotos) geschrieben werden. Wenn Sie sich bei der Ressourcenverwaltung nicht ausschließlich auf RAII verlassen oder nicht, können frühe Rückgaben zu Lecks oder doppeltem Code führen. Vor allem machen es frühe Renditen unpraktisch, Nachbedingungen geltend zu machen.
Adrian McCarthy
72

Die strukturierte Programmierung besagt, dass Sie immer nur eine return-Anweisung pro Funktion haben sollten. Dies soll die Komplexität begrenzen. Viele Leute wie Martin Fowler argumentieren, dass es einfacher ist, Funktionen mit mehreren return-Anweisungen zu schreiben. Er präsentiert dieses Argument in dem klassischen Refactoring- Buch, das er geschrieben hat. Dies funktioniert gut, wenn Sie seinen anderen Ratschlägen folgen und kleine Funktionen schreiben. Ich stimme diesem Standpunkt zu und nur streng strukturierte Programmierpuristen halten sich an einzelne return-Anweisungen pro Funktion.

cdv
quelle
44
Strukturierte Programmierung sagt nichts aus. Einige (aber nicht alle) Leute, die sich als Verfechter strukturierter Programmierung bezeichnen, sagen das.
wnoise
15
"Das funktioniert gut, wenn Sie seinen anderen Ratschlägen folgen und kleine Funktionen schreiben." Dies ist der wichtigste Punkt. Kleine Funktionen benötigen selten viele Rückgabepunkte.
6
@wnoise +1 für den Kommentar, also wahr. Alles, was "Strukturprogrammierung" sagt, ist, kein GOTO zu verwenden.
paxos1977
1
@ceretullis: es sei denn, es ist notwendig. Natürlich ist es nicht unbedingt erforderlich, kann aber in C nützlich sein. Der Linux-Kernel verwendet es aus guten Gründen. GOTO hielt Harmful davon, GOTOden Kontrollfluss auch dann zu bewegen, wenn eine Funktion vorhanden war. Es heißt nie "nie benutzen GOTO".
Esteban Küber
1
"Es geht darum, die 'Struktur' des Codes zu ignorieren." - Nein, ganz im Gegenteil. "Es macht Sinn zu sagen, dass sie vermieden werden sollten" - nein, das tut es nicht.
Jim Balter
62

Wie Kent Beck bei der Erörterung von Schutzklauseln in Implementierungsmustern feststellt , hat eine Routine einen einzigen Ein- und Ausstiegspunkt ...

"war es, die Verwirrung zu vermeiden, die beim Ein- und Aussteigen aus vielen Orten in derselben Routine möglich war. Es war sinnvoll, wenn es auf FORTRAN- oder Assembler-Programme angewendet wurde, die mit vielen globalen Daten geschrieben wurden, bei denen selbst das Verstehen, welche Anweisungen ausgeführt wurden, harte Arbeit war. Mit kleinen Methoden und meist lokalen Daten ist es unnötig konservativ. "

Ich finde eine Funktion, die mit Schutzklauseln geschrieben wurde, viel einfacher zu befolgen als eine lange verschachtelte Reihe von if then elseAnweisungen.

leer
quelle
Natürlich ist "eine lange verschachtelte Reihe von Wenn-Dann-Sonst-Anweisungen" nicht die einzige Alternative zu Schutzklauseln.
Adrian McCarthy
@AdrianMcCarthy hast du eine bessere Alternative? Es wäre nützlicher als Sarkasmus.
Shinzou
@kuhaku: Ich bin nicht sicher, ob ich das Sarkasmus nennen würde. Die Antwort deutet darauf hin, dass es sich um eine Entweder-Oder-Situation handelt: Entweder Schutzklauseln oder lange verschachtelte Bündel von Wenn-Dann-Sonst. Viele (die meisten?) Programmiersprachen bieten neben Schutzklauseln viele Möglichkeiten, diese Logik zu berücksichtigen.
Adrian McCarthy
61

In einer Funktion, die keine Nebenwirkungen hat, gibt es keinen guten Grund, mehr als eine einzige Rückgabe zu haben, und Sie sollten sie in einem funktionalen Stil schreiben. Bei einer Methode mit Nebenwirkungen sind die Dinge sequentieller (zeitindiziert), sodass Sie in einem imperativen Stil schreiben und die return-Anweisung als Befehl verwenden, um die Ausführung zu beenden.

Mit anderen Worten, wenn möglich, bevorzugen Sie diesen Stil

return a > 0 ?
  positively(a):
  negatively(a);

darüber

if (a > 0)
  return positively(a);
else
  return negatively(a);

Wenn Sie feststellen, dass Sie mehrere Ebenen verschachtelter Bedingungen schreiben, können Sie dies wahrscheinlich umgestalten, indem Sie beispielsweise eine Prädikatenliste verwenden. Wenn Sie feststellen, dass Ihr Wenn und Anderes syntaktisch weit voneinander entfernt ist, möchten Sie dies möglicherweise in kleinere Funktionen aufteilen. Ein bedingter Block, der mehr als einen Bildschirm voller Text umfasst, ist schwer zu lesen.

Es gibt keine feste Regel, die für jede Sprache gilt. So etwas wie eine einzige return-Anweisung macht Ihren Code nicht gut. Mit gutem Code können Sie Ihre Funktionen jedoch auf diese Weise schreiben.

Apocalisp
quelle
6
+1 "Wenn Sie feststellen, dass Ihre Ifs und Elses syntaktisch weit voneinander entfernt sind, möchten Sie dies möglicherweise in kleinere Funktionen aufteilen."
Andres Jaan Tack
4
+1, wenn dies ein Problem ist, bedeutet dies normalerweise, dass Sie in einer einzelnen Funktion zu viel tun. Es bedrückt mich wirklich, dass dies nicht die am höchsten gewählte Antwort ist
Matt Briggs
1
Guard Statements haben auch keine Nebenwirkungen, aber die meisten Leute würden sie für nützlich halten. Es kann also Gründe geben, die Ausführung vorzeitig zu beenden, auch wenn keine Nebenwirkungen vorliegen. Diese Antwort spricht das Problem meiner Meinung nach nicht vollständig an.
Maarten Bodewes
@ MaartenBodewes-owlstead Siehe "Strenge und Faulheit"
Apocalisp
43

Ich habe es in Codierungsstandards für C ++ gesehen, die ein Kater von C waren. Wenn Sie nicht über RAII oder eine andere automatische Speicherverwaltung verfügen, müssen Sie für jede Rückgabe bereinigen, was entweder Ausschneiden und Einfügen bedeutet der Bereinigung oder eines goto (logisch das gleiche wie "endlich" in verwalteten Sprachen), die beide als schlechte Form angesehen werden. Wenn Sie in C ++ oder einem anderen automatischen Speichersystem intelligente Zeiger und Sammlungen verwenden möchten, gibt es keinen triftigen Grund dafür, und es geht nur um Lesbarkeit und mehr um eine Beurteilung.

Pete Kirkham
quelle
Gut gesagt, obwohl ich glaube, dass es besser ist, die Löschvorgänge zu kopieren, wenn Sie versuchen, hochoptimierten Code zu schreiben (z. B. Software-Skinning komplexer 3D-Netze!)
Grant Peters
1
Was veranlasst Sie, das zu glauben? Wenn Sie einen Compiler mit schlechter Optimierung haben, bei dem die Dereferenzierung einen gewissen Aufwand verursacht auto_ptr, können Sie einfache Zeiger parallel verwenden. Obwohl es seltsam wäre, überhaupt 'optimierten' Code mit einem nicht optimierenden Compiler zu schreiben.
Pete Kirkham
Dies stellt eine interessante Ausnahme von der Regel dar: Wenn Ihre Programmiersprache nichts enthält, was am Ende der Methode automatisch aufgerufen wird (z. B. try... finallyin Java) und Sie eine Ressourcenwartung durchführen müssen, können Sie dies mit einer einzigen tun Rückkehr am Ende einer Methode. Bevor Sie dies tun, sollten Sie ernsthaft überlegen, den Code zu überarbeiten, um die Situation zu beseitigen.
Maarten Bodewes
@PeteKirkham warum ist goto für die Bereinigung schlecht? Ja, goto kann schlecht verwendet werden, aber diese spezielle Verwendung ist nicht schlecht.
Q126y
1
@ q126y in C ++ schlägt es im Gegensatz zu RAII fehl, wenn eine Ausnahme ausgelöst wird. In C ist es eine vollkommen gültige Praxis. Siehe stackoverflow.com/questions/379172/use-goto-or-not
Pete Kirkham
40

Ich lehne mich an die Idee, dass Rückgabeanweisungen in der Mitte der Funktion schlecht sind. Sie können return verwenden, um ein paar Schutzklauseln am oberen Rand der Funktion zu erstellen, und dem Compiler natürlich mitteilen, was er am Ende der Funktion ohne Probleme zurückgeben soll. Rückgaben in der Mitte der Funktion können jedoch leicht übersehen werden und können erschweren die Interpretation der Funktion.

Joel Coehoorn
quelle
38

Gibt es gute Gründe, warum es besser ist, nur eine return-Anweisung in einer Funktion zu haben?

Ja , es gibt:

  • Der einzelne Ausstiegspunkt bietet einen hervorragenden Ort, um Ihre Nachbedingungen zu bestätigen.
  • Es ist oft nützlich, einen Debugger-Haltepunkt auf die eine Rückgabe am Ende der Funktion setzen zu können.
  • Weniger Renditen bedeuten weniger Komplexität. Linearer Code ist im Allgemeinen einfacher zu verstehen.
  • Wenn der Versuch, eine Funktion auf eine einzige Rückgabe zu vereinfachen, Komplexität verursacht, ist dies ein Anreiz, kleinere, allgemeinere und leichter verständliche Funktionen zu überarbeiten.
  • Wenn Sie in einer Sprache ohne Destruktoren arbeiten oder kein RAII verwenden, reduziert eine einzelne Rückgabe die Anzahl der Stellen, die Sie bereinigen müssen.
  • Einige Sprachen erfordern einen einzigen Austrittspunkt (z. B. Pascal und Eiffel).

Die Frage wird oft als falsche Zweiteilung zwischen mehreren Rückgaben oder tief verschachtelten if-Anweisungen gestellt. Es gibt fast immer eine dritte Lösung, die sehr linear ist (keine tiefe Verschachtelung) und nur einen einzigen Austrittspunkt hat.

Update : Anscheinend fördern die MISRA-Richtlinien auch den Single-Exit .

Um es klar zu sagen, ich sage nicht, dass es immer falsch ist, mehrere Retouren zu haben. Angesichts ansonsten gleichwertiger Lösungen gibt es viele gute Gründe, die mit einer einzigen Rendite zu bevorzugen.

Adrian McCarthy
quelle
2
Ein weiterer guter Grund, der heutzutage wahrscheinlich der beste ist, eine einzige Rückgabeanweisung zu haben, ist die Protokollierung. Wenn Sie einer Methode eine Protokollierung hinzufügen möchten, können Sie eine einzelne Protokollanweisung platzieren, die die Rückgabe der Methode angibt.
Inor
Wie häufig war die FORTRAN ENTRY-Anweisung? Siehe docs.oracle.com/cd/E19957-01/805-4939/6j4m0vn99/index.html . Und wenn Sie abenteuerlustig sind, können Sie Methoden mit AOP und
Nachrat
1
+1 Die ersten 2 Punkte haben mich überzeugt. Das mit dem vorletzten Absatz. Ich würde dem Protokollierungselement aus dem gleichen Grund nicht zustimmen, da ich tief verschachtelte Bedingungen entmutige, da sie dazu ermutigen, die Regel der Einzelverantwortung zu brechen, die der Hauptgrund dafür ist, dass Polymorphismus in OOPs eingeführt wurde.
Francis Rodgers
Ich möchte nur hinzufügen, dass bei C # - und Code-Verträgen das Problem nach der Bedingung kein Problem ist, da Sie es immer noch Contract.Ensuresmit mehreren Rückgabepunkten verwenden können.
Julealgon
1
@ q126y: Wenn Sie gotoden allgemeinen Bereinigungscode verwenden, haben Sie die Funktion wahrscheinlich so vereinfacht, dass returnam Ende des Bereinigungscodes eine einzige angezeigt wird . Sie könnten also sagen, dass Sie das Problem mit gelöst haben goto, aber ich würde sagen, dass Sie es durch Vereinfachung auf ein einziges gelöst haben return.
Adrian McCarthy
33

Ein einzelner Exit-Punkt bietet einen Vorteil beim Debuggen, da Sie einen einzelnen Haltepunkt am Ende einer Funktion festlegen können, um zu sehen, welcher Wert tatsächlich zurückgegeben wird.

Mark
quelle
6
BRAVO! Sie sind die einzige Person, die diesen objektiven Grund erwähnt. Aus diesem Grund bevorzuge ich einzelne Ausstiegspunkte gegenüber mehreren Ausstiegspunkten. Wenn mein Debugger an einem Austrittspunkt einen Haltepunkt setzen könnte, würde ich wahrscheinlich mehrere Austrittspunkte bevorzugen. Meine aktuelle Meinung ist, dass die Leute, die mehrere Exit-Punkte codieren, dies zu ihrem eigenen Vorteil tun, auf Kosten derjenigen anderen, die einen Debugger für ihren Code verwenden müssen (und ja, ich spreche über alle Open-Source-Mitwirkenden, mit denen Code geschrieben wird mehrere Ausstiegspunkte.)
MikeSchinkel
3
JA. Ich füge Protokollierungscode zu einem System hinzu, das sich in der Produktion zeitweise schlecht verhält (wo ich nicht durchgehen kann). Wenn der vorherige Codierer Single-Exit verwendet hätte, wäre es VIEL einfacher gewesen.
Michael Blackburn
3
Richtig, beim Debuggen ist es hilfreich. In der Praxis konnte ich jedoch in den meisten Fällen den Haltepunkt in der aufrufenden Funktion unmittelbar nach dem Aufruf setzen - effektiv zum gleichen Ergebnis. (Und diese Position befindet sich natürlich auf dem Aufrufstapel.) YMMV.
foo
Dies gilt nur, wenn Ihr Debugger eine Step-Out- oder Step-Return-Funktion bereitstellt (und jeder Debugger tut dies, soweit ich weiß), die den zurückgegebenen Wert direkt nach der Rückgabe anzeigt . Das anschließende Ändern des Werts kann etwas schwierig sein, wenn er keiner Variablen zugewiesen ist.
Maarten Bodewes
7
Ich habe lange Zeit keinen Debugger mehr gesehen, der es Ihnen nicht erlaubt, einen Haltepunkt am "Ende" der Methode festzulegen (Ende, rechte geschweifte Klammer, unabhängig von Ihrer Sprache) und diesen Haltepunkt zu erreichen, unabhängig davon, wo oder wie viele , Rückgabestatemets sind in der Methode. Selbst wenn Ihre Funktion nur eine Rückgabe hat, bedeutet dies nicht, dass Sie die Funktion nicht mit einer Ausnahme (explizit oder geerbt) beenden können. Ich denke, das ist kein wirklich gültiger Punkt.
Scott Gartner
19

Im Allgemeinen versuche ich, nur einen einzigen Austrittspunkt von einer Funktion zu haben. Es gibt jedoch Situationen, in denen dadurch tatsächlich ein komplexerer Funktionskörper als erforderlich erstellt wird. In diesem Fall ist es besser, mehrere Austrittspunkte zu haben. Es muss wirklich ein "Urteilsspruch" sein, der auf der resultierenden Komplexität basiert, aber das Ziel sollte so wenig Ausgangspunkte wie möglich sein, ohne die Komplexität und Verständlichkeit zu beeinträchtigen.

Scott Dorman
quelle
"Im Allgemeinen versuche ich, nur einen einzigen Austrittspunkt von einer Funktion zu haben" - warum? "Das Ziel sollten so wenige Ausstiegspunkte wie möglich sein" - warum? Und warum haben 19 Personen diese Nichtantwort abgelehnt?
Jim Balter
@ JimBalter Letztendlich läuft es auf persönliche Vorlieben hinaus. Mehr Austrittspunkte führen normalerweise zu einer komplexeren Methode (wenn auch nicht immer) und erschweren das Verständnis für jemanden.
Scott Dorman
"Es läuft auf persönliche Vorlieben hinaus." - Mit anderen Worten, Sie können keinen Grund angeben. "Mehr Austrittspunkte führen normalerweise zu einer komplexeren Methode (wenn auch nicht immer)" - Nein, tatsächlich nicht. Bei zwei logisch äquivalenten Funktionen, eine mit Schutzklauseln und eine mit einem einzigen Ausgang, weist letztere eine höhere zyklomatische Komplexität auf. Zahlreiche Studien zeigen Ergebnisse in Code, der fehleranfälliger und schwer zu verstehen ist. Sie würden davon profitieren, die anderen Antworten hier zu lesen.
Jim Balter
14

Nein, weil wir nicht mehr in den 1970ern leben . Wenn Ihre Funktion lang genug ist, dass mehrere Rückgaben ein Problem darstellen, ist sie zu lang.

(Abgesehen von der Tatsache, dass jede mehrzeilige Funktion in einer Sprache mit Ausnahmen ohnehin mehrere Austrittspunkte hat.)

Ben Lings
quelle
14

Ich würde einen einzelnen Ausgang bevorzugen, es sei denn, dies erschwert die Dinge wirklich. Ich habe festgestellt, dass in einigen Fällen mehrere vorhandene Punkte andere wichtigere Entwurfsprobleme maskieren können:

public void DoStuff(Foo foo)
{
    if (foo == null) return;
}

Wenn ich diesen Code sehe, würde ich sofort fragen:

  • Ist 'foo' jemals null?
  • Wenn ja, wie viele Clients von 'DoStuff' rufen die Funktion jemals mit einem Null-Foo auf?

Abhängig von den Antworten auf diese Fragen kann es sein, dass

  1. Die Prüfung ist sinnlos, da sie niemals wahr ist (dh es sollte eine Behauptung sein).
  2. Die Prüfung ist sehr selten wahr und daher ist es möglicherweise besser, diese spezifischen Anruferfunktionen zu ändern, da sie wahrscheinlich sowieso andere Maßnahmen ergreifen sollten.

In beiden oben genannten Fällen kann der Code wahrscheinlich mit einer Zusicherung überarbeitet werden, um sicherzustellen, dass 'foo' niemals null ist und die relevanten Anrufer geändert werden.

Es gibt zwei weitere Gründe (speziell für C ++ - Code), bei denen mehrere tatsächlich negative Auswirkungen haben können. Sie sind Codegröße und Compiler-Optimierungen.

Bei einem Nicht-POD-C ++ - Objekt im Gültigkeitsbereich am Ende einer Funktion wird der Destruktor aufgerufen. Bei mehreren return-Anweisungen kann es vorkommen, dass sich unterschiedliche Objekte im Gültigkeitsbereich befinden und die Liste der aufzurufenden Destruktoren daher unterschiedlich ist. Der Compiler muss daher für jede return-Anweisung Code generieren:

void foo (int i, int j) {
  A a;
  if (i > 0) {
     B b;
     return ;   // Call dtor for 'b' followed by 'a'
  }
  if (i == j) {
     C c;
     B b;
     return ;   // Call dtor for 'b', 'c' and then 'a'
  }
  return 'a'    // Call dtor for 'a'
}

Wenn die Codegröße ein Problem darstellt, sollte dies vermieden werden.

Das andere Problem betrifft "Named Return Value OptimiZation" (auch bekannt als Copy Elision, ISO C ++ '03 12.8 / 15). Mit C ++ kann eine Implementierung den Aufruf des Kopierkonstruktors überspringen, wenn dies möglich ist:

A foo () {
  A a1;
  // do something
  return a1;
}

void bar () {
  A a2 ( foo() );
}

Wenn man den Code so nimmt, wie er ist, wird das Objekt 'a1' in 'foo' konstruiert und dann wird sein Kopierkonstrukt aufgerufen, um 'a2' zu konstruieren. Mit Copy Elision kann der Compiler jedoch 'a1' an derselben Stelle auf dem Stapel wie 'a2' erstellen. Es ist daher nicht erforderlich, das Objekt zu "kopieren", wenn die Funktion zurückkehrt.

Mehrere Exit-Punkte erschweren die Arbeit des Compilers, dies zu erkennen, und zumindest für eine relativ neue Version von VC ++ fand die Optimierung nicht statt, wenn der Funktionskörper mehrere Rückgaben hatte. Siehe Named Rückgabewert Optimierung in Visual C ++ 2005 für weitere Details.

Richard Corden
quelle
1
Wenn Sie alle bis auf den letzten dtor aus Ihrem C ++ - Beispiel entfernen, muss der Code zum Zerstören von B und später von C und B immer noch generiert werden, wenn der Gültigkeitsbereich der if-Anweisung endet, sodass Sie wirklich nichts gewinnen, wenn Sie nicht mehrere Rückgaben haben .
Eclipse
4
+1 Und waaaaay unten in der Liste haben wir den WIRKLICHEN Grund, warum diese Codierungspraxis existiert - NRVO. Dies ist jedoch eine Mikrooptimierung; und wurde, wie alle Mikrooptimierungspraktiken, wahrscheinlich von einem 50-jährigen "Experten" gestartet, der es gewohnt ist, auf einem 300-kHz-PDP-8 zu programmieren, und der die Bedeutung von sauberem und strukturiertem Code nicht versteht. Befolgen Sie im Allgemeinen den Rat von Chris S und verwenden Sie mehrere return-Anweisungen, wann immer dies sinnvoll ist.
BlueRaja - Danny Pflughoeft
Obwohl ich Ihrer Präferenz nicht zustimme (meiner Meinung nach ist Ihr Assert-Vorschlag auch ein Rückgabepunkt, wie throw new ArgumentNullException()in diesem Fall ein C #), hat mir Ihre andere Überlegung sehr gut gefallen, sie sind alle für mich gültig und könnten in einigen Fällen kritisch sein Nischenkontexte.
Julealgon
Das ist voll von Strohmännern. Die Frage, warum foogetestet wird, hat nichts mit dem Thema zu tun, nämlich ob if (foo == NULL) return; dowork; oderif (foo != NULL) { dowork; }
Jim Balter
11

Ein einziger Austrittspunkt verringert die zyklomatische Komplexität und damit theoretisch die Wahrscheinlichkeit, dass Sie Fehler in Ihren Code einfügen, wenn Sie ihn ändern. Die Praxis deutet jedoch eher darauf hin, dass ein pragmatischerer Ansatz erforderlich ist. Ich tendiere daher dazu, einen einzigen Austrittspunkt anzustreben, erlaube meinem Code jedoch, mehrere zu haben, wenn dies besser lesbar ist.

John Channing
quelle
Sehr aufschlussreich. Obwohl ich der Meinung bin, dass ein Programmierer, bis er weiß, wann er mehrere Exit-Punkte verwenden soll, auf einen beschränkt sein sollte.
Rick Minerich
5
Nicht wirklich. Die zyklomatische Komplexität von "if (...) return; ... return;" ist dasselbe wie "if (...) {...} return;". Sie haben beide zwei Wege durch sich.
Steve Emmerson
11

Ich zwinge mich, nur eine returnAnweisung zu verwenden, da dies gewissermaßen Codegeruch erzeugt. Lassen Sie mich erklären:

function isCorrect($param1, $param2, $param3) {
    $toret = false;
    if ($param1 != $param2) {
        if ($param1 == ($param3 * 2)) {
            if ($param2 == ($param3 / 3)) {
                $toret = true;
            } else {
                $error = 'Error 3';
            }
        } else {
            $error = 'Error 2';
        }
    } else {
        $error = 'Error 1';
    }
    return $toret;
}

(Die Bedingungen sind arbritary ...)

Je mehr Bedingungen, je größer die Funktion wird, desto schwieriger ist das Lesen. Wenn Sie also auf den Codegeruch eingestellt sind, werden Sie ihn erkennen und den Code umgestalten wollen. Zwei mögliche Lösungen sind:

  • Mehrere Rückgaben
  • Refactoring in separate Funktionen

Mehrere Rückgaben

function isCorrect($param1, $param2, $param3) {
    if ($param1 == $param2)       { $error = 'Error 1'; return false; }
    if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
    if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
    return true;
}

Separate Funktionen

function isEqual($param1, $param2) {
    return $param1 == $param2;
}

function isDouble($param1, $param2) {
    return $param1 == ($param2 * 2);
}

function isThird($param1, $param2) {
    return $param1 == ($param2 / 3);
}

function isCorrect($param1, $param2, $param3) {
    return !isEqual($param1, $param2)
        && isDouble($param1, $param3)
        && isThird($param2, $param3);
}

Zugegeben, es ist länger und etwas chaotisch, aber wir haben gerade dabei, die Funktion auf diese Weise umzugestalten

  • eine Reihe von wiederverwendbaren Funktionen erstellt,
  • machte die Funktion menschlicher lesbar, und
  • Der Fokus der Funktionen liegt darauf, warum die Werte korrekt sind.
Jrgns
quelle
5
-1: Schlechtes Beispiel. Sie haben die Behandlung von Fehlermeldungen weggelassen. Wenn dies nicht benötigt würde, könnte isCorrect genauso ausgedrückt werden wie return xx && yy && zz; Dabei sind xx, yy und z die Ausdrücke isEqual, isDouble und isThird.
Kauppi
10

Ich würde sagen, Sie sollten so viele haben, wie erforderlich, oder alle, die den Code sauberer machen (wie z. B. Schutzklauseln ).

Ich persönlich habe noch nie gehört, dass "Best Practices" besagen, dass Sie nur eine Rückgabeerklärung haben sollten.

Zum größten Teil neige ich dazu, eine Funktion auf der Grundlage eines Logikpfads so schnell wie möglich zu verlassen (Schutzklauseln sind ein hervorragendes Beispiel dafür).

Rob Cooper
quelle
10

Ich glaube, dass mehrere Rückgaben normalerweise gut sind (in dem Code, den ich in C # schreibe). Der Single-Return-Stil ist ein Überbleibsel von C. Aber Sie codieren wahrscheinlich nicht in C.

Es gibt kein Gesetz, das nur einen Austrittspunkt für eine Methode in allen Programmiersprachen vorschreibt . Einige Leute bestehen auf der Überlegenheit dieses Stils, und manchmal erheben sie ihn zu einer "Regel" oder einem "Gesetz", aber dieser Glaube wird nicht durch Beweise oder Forschungen gestützt.

Mehr als ein Rückgabestil kann eine schlechte Angewohnheit in C-Code sein, wo Ressourcen explizit freigegeben werden müssen, aber Sprachen wie Java, C #, Python oder JavaScript, die Konstrukte wie automatische Garbage Collection und try..finallyBlöcke (und usingBlöcke in C #) enthalten ), und dieses Argument trifft nicht zu - in diesen Sprachen ist es sehr ungewöhnlich, dass eine zentralisierte manuelle Freigabe von Ressourcen erforderlich ist.

Es gibt Fälle, in denen eine einzelne Rückgabe besser lesbar ist, und Fälle, in denen dies nicht der Fall ist. Überprüfen Sie, ob dadurch die Anzahl der Codezeilen verringert, die Logik klarer oder die Anzahl der geschweiften Klammern und Einrückungen oder temporären Variablen verringert wird.

Verwenden Sie daher so viele Retouren, wie es Ihrer künstlerischen Sensibilität entspricht, da es sich um ein Layout- und Lesbarkeitsproblem handelt, nicht um ein technisches.

Ich habe in meinem Blog ausführlicher darüber gesprochen .

Anthony
quelle
10

Es gibt gute Dinge über einen einzelnen Austrittspunkt zu sagen, genauso wie es schlechte Dinge über die unvermeidliche "Pfeil" -Programmierung zu sagen gibt , die sich daraus ergibt.

Wenn Sie während der Eingabevalidierung oder der Ressourcenzuweisung mehrere Exit-Punkte verwenden, versuche ich, alle "Fehler-Exits" sehr gut oben in der Funktion zu platzieren.

Sowohl der Spartan Programming- Artikel der "SSDSLPedia" als auch der Single Function Exit Point- Artikel des "Portland Pattern Repository's Wiki" enthalten einige aufschlussreiche Argumente. Natürlich gibt es auch diesen Beitrag zu beachten.

Wenn Sie beispielsweise wirklich einen einzelnen Exit-Punkt (in einer nicht ausnahmefähigen Sprache) möchten, um Ressourcen an einem einzigen Ort freizugeben, finde ich die sorgfältige Anwendung von goto gut. siehe zum Beispiel dieses eher erfundene Beispiel (komprimiert, um Bildschirmfläche zu sparen):

int f(int y) {
    int value = -1;
    void *data = NULL;

    if (y < 0)
        goto clean;

    if ((data = malloc(123)) == NULL)
        goto clean;

    /* More code */

    value = 1;
clean:
   free(data);
   return value;
}

Persönlich mag ich Pfeilprogrammierung im Allgemeinen nicht mehr als mehrere Austrittspunkte, obwohl beide nützlich sind, wenn sie richtig angewendet werden. Das Beste ist natürlich, Ihr Programm so zu strukturieren, dass es keines von beiden benötigt. Die Aufteilung Ihrer Funktion in mehrere Teile hilft normalerweise :)

Obwohl ich dabei ohnehin mehrere Austrittspunkte habe, wie in diesem Beispiel, wo einige größere Funktionen in mehrere kleinere Funktionen unterteilt wurden:

int g(int y) {
  value = 0;

  if ((value = g0(y, value)) == -1)
    return -1;

  if ((value = g1(y, value)) == -1)
    return -1;

  return g2(y, value);
}

Abhängig vom Projekt oder den Codierungsrichtlinien kann der größte Teil des Kesselplattencodes durch Makros ersetzt werden. Nebenbei bemerkt, wenn man es auf diese Weise aufschlüsselt, können die Funktionen g0, g1, g2 sehr einfach einzeln getestet werden.

Offensichtlich würde ich in einer OO- und ausnahmefähigen Sprache solche if-Anweisungen nicht verwenden (oder überhaupt, wenn ich mit wenig Aufwand damit durchkommen könnte), und der Code wäre viel einfacher. Und nicht pfeilig. Und die meisten nicht endgültigen Renditen wären wahrscheinlich Ausnahmen.

Zusamenfassend;

  • Nur wenige Renditen sind besser als viele Renditen
  • Mehr als eine Rückgabe ist besser als große Pfeile, und Schutzklauseln sind im Allgemeinen in Ordnung.
  • Ausnahmen könnten / sollten wahrscheinlich die meisten "Schutzklauseln" ersetzen, wenn dies möglich ist.
Henrik Gustafsson
quelle
Das Beispiel stürzt für y <0 ab, weil es versucht, den NULL-Zeiger
freizugeben ;-)
2
opengroup.org/onlinepubs/009695399/functions/free.html "Wenn ptr ein Nullzeiger ist, darf keine Aktion ausgeführt werden."
Henrik Gustafsson
1
Nein, es wird nicht abstürzen, da die Übergabe von NULL an free ein definiertes No-Op ist. Es ist ein ärgerlich häufiges Missverständnis, dass Sie zuerst auf NULL testen müssen.
Hlovdal
Das "Pfeil" -Muster ist keine unvermeidliche Alternative. Dies ist eine falsche Zweiteilung.
Adrian McCarthy
9

Sie kennen das Sprichwort - Schönheit liegt in den Augen des Betrachters .

Einige schwören auf NetBeans und andere auf IntelliJ IDEA , andere auf Python und andere auf PHP .

In einigen Geschäften könnten Sie Ihren Job verlieren, wenn Sie darauf bestehen:

public void hello()
{
   if (....)
   {
      ....
   }
}

Die Frage dreht sich alles um Sichtbarkeit und Wartbarkeit.

Ich bin süchtig nach boolescher Algebra, um die Logik und den Gebrauch von Zustandsmaschinen zu reduzieren und zu vereinfachen. Es gab jedoch frühere Kollegen, die meinten, mein Einsatz von "mathematischen Techniken" beim Codieren sei ungeeignet, weil er nicht sichtbar und wartbar wäre. Und das wäre eine schlechte Praxis. Tut mir leid, die Techniken, die ich verwende, sind für mich sehr sichtbar und wartbar - denn wenn ich sechs Monate später zum Code zurückkehre, würde ich den Code klar verstehen und eher ein Durcheinander sprichwörtlicher Spaghetti sehen.

Hey Kumpel (wie ein ehemaliger Kunde sagte), mach was du willst, solange du weißt, wie man es repariert, wenn du es reparieren musst.

Ich erinnere mich, dass vor 20 Jahren ein Kollege von mir entlassen wurde, weil er eine so genannte agile Entwicklungsstrategie angewendet hatte. Er hatte einen akribischen inkrementellen Plan. Aber sein Manager schrie ihn an: "Sie können Funktionen nicht schrittweise für Benutzer freigeben! Sie müssen sich an den Wasserfall halten ." Seine Antwort an den Manager war, dass die schrittweise Entwicklung genauer auf die Bedürfnisse des Kunden zugeschnitten sein würde. Er glaubte an die Entwicklung für die Kundenbedürfnisse, aber der Manager glaubte an die Codierung nach "Kundenanforderungen".

Wir sind häufig schuldig, Datennormalisierungs-, MVP- und MVC- Grenzen überschritten zu haben. Wir inline anstatt eine Funktion zu konstruieren. Wir nehmen Abkürzungen.

Persönlich glaube ich, dass PHP eine schlechte Praxis ist, aber was weiß ich. Alle theoretischen Argumente laufen darauf hinaus, einen Satz von Regeln zu erfüllen

Qualität = Präzision, Wartbarkeit und Rentabilität.

Alle anderen Regeln treten in den Hintergrund. Und natürlich verblasst diese Regel nie:

Faulheit ist die Tugend eines guten Programmierers.

Blessed Geek
quelle
1
"Hey Kumpel (wie ein ehemaliger Kunde sagte), mach was du willst, solange du weißt, wie man es repariert, wenn du es reparieren musst." Problem: Oft sind es nicht Sie, die das Problem beheben.
Dan Barron
+1 auf diese Antwort, weil ich damit einverstanden bin, wohin Sie gehen, aber nicht unbedingt, wie Sie dorthin gelangen. Ich würde argumentieren, dass es Ebenen des Verständnisses gibt. Das Verständnis, dass Mitarbeiter A nach 5 Jahren Programmiererfahrung und 5 Jahren in einem Unternehmen über ein anderes Verständnis verfügt als Mitarbeiter B, ein neuer Hochschulabsolvent, der gerade mit einem Unternehmen anfängt. Mein Punkt wäre, dass wenn Mitarbeiter A die einzigen Personen sind, die den Code verstehen können, er NICHT wartbar ist und wir uns daher alle bemühen sollten, Code zu schreiben, den Mitarbeiter B verstehen kann. Hier liegt die wahre Kunst in der Software.
Francis Rodgers
9

Ich neige dazu, Schutzklauseln zu verwenden, um früh zurückzukehren und ansonsten am Ende einer Methode zu beenden. Die Einzeleintritts- und -ausgangsregel hat historische Bedeutung und war besonders hilfreich, wenn es sich um Legacy-Code handelte, der für eine einzelne C ++ - Methode mit mehreren Rückgaben (und vielen Fehlern) 10 A4-Seiten umfasste. In jüngerer Zeit wird empfohlen, Methoden klein zu halten, wodurch Mehrfachausgänge die Verständnisimpedanz verringern. Im folgenden von oben kopierten Kronoz-Beispiel lautet die Frage, was in // Rest des Codes vorkommt ... ?:

void string fooBar(string s, int? i) {

  if(string.IsNullOrEmpty(s) || i == null) return null;

  var res = someFunction(s, i);

  foreach(var r in res) {
      if(!r.Passed) return null;
  }

  // Rest of code...

  return ret;
}

Mir ist klar, dass das Beispiel etwas erfunden ist, aber ich wäre versucht, die foreach- Schleife in eine LINQ-Anweisung umzuwandeln, die dann als Schutzklausel betrachtet werden könnte. Auch hier ist die Absicht des Codes in einem konstruiertes Beispiel nicht ersichtlich und Funktion () Wirkung eine andere Seite hat in der verwendet wird oder kann das Ergebnis // Rest des Code ... .

if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;

Geben Sie die folgende überarbeitete Funktion:

void string fooBar(string s, int? i) {

  if (string.IsNullOrEmpty(s) || i == null) return null;
  if (someFunction(s, i).Any(r => !r.Passed)) return null;

  // Rest of code...

  return ret;
}
David Clarke
quelle
Hat C ++ keine Ausnahmen? Warum sollten Sie dann zurückkehren, nullanstatt eine Ausnahme auszulösen, die angibt, dass das Argument nicht akzeptiert wird?
Maarten Bodewes
1
Wie bereits erwähnt, wird der Beispielcode aus einer früheren Antwort ( stackoverflow.com/a/36729/132599 ) kopiert . Das ursprüngliche Beispiel gab Nullen zurück, und das Refactoring zum Auslösen von Argumentausnahmen war bis zu dem Punkt, den ich zu machen versuchte, oder zu der ursprünglichen Frage nicht wesentlich. Aus bewährten Gründen würde ich normalerweise (in C #) eine ArgumentNullException in eine Guard-Klausel werfen, anstatt einen Nullwert zurückzugeben.
David Clarke
7

Ein guter Grund, den ich mir vorstellen kann, ist die Code-Wartung: Sie haben einen einzigen Ausgangspunkt. Wenn Sie das Format des Ergebnisses ändern möchten, ... ist die Implementierung viel einfacher. Zum Debuggen können Sie dort einfach einen Haltepunkt setzen :)

Trotzdem musste ich einmal in einer Bibliothek arbeiten, in der die Codierungsstandards "eine Rückgabeanweisung pro Funktion" vorschrieben, und ich fand es ziemlich schwierig. Ich schreibe viel numerischen Berechnungscode, und es gibt oft "Sonderfälle", so dass der Code ziemlich schwer zu befolgen war ...

OysterD
quelle
Das macht keinen wirklichen Unterschied. Wenn Sie den Typ der zurückgegebenen lokalen Variablen ändern, müssen Sie alle Zuordnungen zu dieser lokalen Variablen korrigieren. Und es ist wahrscheinlich besser, eine Methode mit einer anderen Signatur zu definieren, da Sie auch alle Methodenaufrufe korrigieren müssen.
Maarten Bodewes
@ MaartenBodewes-owlstead - Es kann einen Unterschied machen. Um nur zwei Beispiele zu nennen, bei denen Sie nicht alle Zuordnungen zur lokalen Variablen korrigieren oder die Methodenaufrufe ändern müssten, könnte die Funktion ein Datum als Zeichenfolge zurückgeben (die lokale Variable wäre ein tatsächliches Datum, das nur als Zeichenfolge formatiert ist im letzten Moment), oder es wird möglicherweise eine Dezimalzahl zurückgegeben, und Sie möchten die Anzahl der Dezimalstellen ändern.
nnnnnn
@nnnnnn OK, wenn Sie die Nachbearbeitung für die Ausgabe durchführen möchten ... Aber ich würde nur eine neue Methode generieren, die die Nachbearbeitung ausführt, und die alte in Ruhe lassen. Das ist nur geringfügig schwieriger zu überarbeiten, aber Sie müssen überprüfen, ob die anderen Aufrufe trotzdem mit dem neuen Format kompatibel sind. Aber es ist trotzdem ein triftiger Grund.
Maarten Bodewes
7

Mehrere Austrittspunkte sind für ausreichend kleine Funktionen ausreichend - dh eine Funktion, die insgesamt auf einer Bildschirmlänge angezeigt werden kann. Wenn eine lange Funktion ebenfalls mehrere Austrittspunkte enthält, ist dies ein Zeichen dafür, dass die Funktion weiter zerlegt werden kann.

Das heißt, ich vermeide Mehrfach-Exit-Funktionen, es sei denn, dies ist absolut notwendig . Ich habe Schmerzen von Fehlern verspürt, die auf eine streunende Rückkehr in einer dunklen Linie in komplexeren Funktionen zurückzuführen sind.

Jon Limjap
quelle
6

Ich habe mit schrecklichen Codierungsstandards gearbeitet, die Ihnen einen einzigen Exit-Pfad aufgezwungen haben, und das Ergebnis sind fast immer unstrukturierte Spaghetti, wenn die Funktion alles andere als trivial ist - Sie haben am Ende viele Pausen und fahren fort, die Ihnen nur im Weg stehen.

Phil Bennett
quelle
Ganz zu schweigen davon, dass Sie Ihre Meinung sagen müssen, dass Sie die ifAussage vor jedem Methodenaufruf überspringen sollen, der Erfolg bringt oder nicht :(
Maarten Bodewes
6

Ein einziger Austrittspunkt - alle anderen Dinge sind gleich - macht den Code deutlich lesbarer. Aber es gibt einen Haken: beliebte Konstruktion

resulttype res;
if if if...
return res;

ist eine Fälschung, "res =" ist nicht viel besser als "return". Es hat eine einzelne return-Anweisung, aber mehrere Punkte, an denen die Funktion tatsächlich endet.

Wenn Sie eine Funktion mit mehreren Rückgaben (oder "res =" s) haben, ist es oft eine gute Idee, diese in mehrere kleinere Funktionen mit einem einzigen Austrittspunkt aufzuteilen.

ima
quelle
6

Meine übliche Richtlinie besteht darin, nur eine return-Anweisung am Ende einer Funktion zu haben, es sei denn, die Komplexität des Codes wird durch Hinzufügen weiterer Anweisungen erheblich verringert. Tatsächlich bin ich eher ein Fan von Eiffel, das die einzige Rückgaberegel erzwingt, indem es keine Rückgabeanweisung hat (es gibt nur eine automatisch erstellte 'Ergebnis'-Variable, in die Sie Ihr Ergebnis einfügen können).

Es gibt sicherlich Fälle, in denen Code mit mehreren Rückgaben klarer dargestellt werden kann als die offensichtliche Version ohne sie. Man könnte argumentieren, dass mehr Nacharbeit erforderlich ist, wenn Sie eine Funktion haben, die zu komplex ist, um ohne mehrere return-Anweisungen verständlich zu sein, aber manchmal ist es gut, in solchen Dingen pragmatisch zu sein.

Matt Sheppard
quelle
5

Wenn Sie am Ende mehr als ein paar Retouren erhalten, stimmt möglicherweise etwas mit Ihrem Code nicht. Ansonsten würde ich zustimmen, dass es manchmal schön ist, von mehreren Stellen in einem Unterprogramm zurückkehren zu können, insbesondere wenn dadurch der Code sauberer wird.

Perl 6: Schlechtes Beispiel

sub Int_to_String( Int i ){
  given( i ){
    when 0 { return "zero" }
    when 1 { return "one" }
    when 2 { return "two" }
    when 3 { return "three" }
    when 4 { return "four" }
    ...
    default { return undef }
  }
}

wäre besser so geschrieben

Perl 6: Gutes Beispiel

@Int_to_String = qw{
  zero
  one
  two
  three
  four
  ...
}
sub Int_to_String( Int i ){
  return undef if i < 0;
  return undef unless i < @Int_to_String.length;
  return @Int_to_String[i]
}

Beachten Sie, dass dies nur ein kurzes Beispiel ist

Brad Gilbert
quelle
Ok Warum wurde das abgelehnt? Es ist nicht so, dass es keine Meinung ist.
Brad Gilbert
5

Ich stimme für Single Return am Ende als Richtlinie. Dies hilft bei einer allgemeinen Code-Bereinigung ... Sehen Sie sich beispielsweise den folgenden Code an ...

void ProcessMyFile (char *szFileName)
{
   FILE *fp = NULL;
   char *pbyBuffer = NULL:

   do {

      fp = fopen (szFileName, "r");

      if (NULL == fp) {

         break;
      }

      pbyBuffer = malloc (__SOME__SIZE___);

      if (NULL == pbyBuffer) {

         break;
      }

      /*** Do some processing with file ***/

   } while (0);

   if (pbyBuffer) {

      free (pbyBuffer);
   }

   if (fp) {

      fclose (fp);
   }
}
Alphaneo
quelle
Sie stimmen für eine einzelne Rückgabe - im C-Code. Aber was wäre, wenn Sie in einer Sprache mit Garbage Collection codieren und versuchen würden, sie endgültig zu blockieren?
Anthony
4

Dies ist wahrscheinlich eine ungewöhnliche Perspektive, aber ich denke, dass jeder, der glaubt, dass mehrere return-Anweisungen bevorzugt werden sollen, noch nie einen Debugger auf einem Mikroprozessor verwenden musste, der nur 4 Hardware-Haltepunkte unterstützt. ;-);

Während die Probleme mit "Pfeilcode" vollständig korrekt sind, scheint ein Problem, das bei Verwendung mehrerer return-Anweisungen zu verschwinden scheint, in der Situation zu liegen, in der Sie einen Debugger verwenden. Sie haben keine bequeme Sammelposition, um einen Haltepunkt zu setzen, um sicherzustellen, dass Sie den Ausgang und damit die Rückgabebedingung sehen.

Andrew Edgecombe
quelle
5
Das ist nur eine andere Art der vorzeitigen Optimierung. Sie sollten niemals für den Sonderfall optimieren. Wenn Sie feststellen, dass Sie einen bestimmten Codeabschnitt häufig debuggen, ist daran mehr falsch als nur an der Anzahl der Exit-Punkte.
Wedge
Kommt auch auf deinen Debugger an.
Maarten Bodewes
4

Je mehr return-Anweisungen Sie in einer Funktion haben, desto komplexer ist diese eine Methode. Wenn Sie sich fragen, ob Sie zu viele return-Anweisungen haben, möchten Sie sich möglicherweise fragen, ob diese Funktion zu viele Codezeilen enthält.

Aber nicht, es ist nichts falsch an einer / mehreren return-Anweisungen. In einigen Sprachen ist es eine bessere Praxis (C ++) als in anderen (C).

Hazzen
quelle