Frage

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?

War es hilfreich?

Lösung

Ich habe am Anfang einer Methode oft mehrere Anweisungen, die in „einfachen“ Situationen zurückgegeben werden sollen.Zum Beispiel dies:

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

...kann meiner Meinung nach wie folgt lesbarer gemacht werden:

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

    ...
}

Also ja, ich denke, es ist in Ordnung, mehrere „Ausstiegspunkte“ von einer Funktion/Methode zu haben.

Andere Tipps

Niemand hat es erwähnt oder zitiert Code abgeschlossen 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 die Möglichkeit besteht, dass sie irgendwo oben zurückgekehrt ist.

Benutze einen zurückkehren wenn es 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 das Nicht-Rückkehren, dass Sie mehr Code schreiben müssen.

Ich würde sagen, dass es unglaublich unklug wäre, sich willkürlich gegen mehrere Ausstiegspunkte zu entscheiden, da ich festgestellt habe, dass sich die Technik in der Praxis als nützlich erwiesen hat wieder und wieder, tatsächlich habe ich das oft getan Vorhandenen Code überarbeitet aus Gründen der Übersichtlichkeit auf mehrere Ausgangspunkte verweisen.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 mit mehreren Ausstiegspunkten Sind gestattet:-

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 Ausstiegspunkten heutzutage eine eher archaische Sichtweise.

Ich arbeite derzeit an einer Codebasis, bei der zwei der Leute, die daran arbeiten, blind der „Single Point of Exit“-Theorie zustimmen, und ich kann Ihnen aus Erfahrung sagen, dass es eine schreckliche, schreckliche Praxis ist.Dadurch wird die Wartung des Codes extrem schwierig, und ich zeige Ihnen, warum.

Mit der „Single Point of Exit“-Theorie landet man unweigerlich bei 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;
}

Dadurch ist der Code nicht nur sehr schwer zu verstehen, sondern Sie müssen auch später noch einmal zurückgehen und eine Operation zwischen 1 und 2 hinzufügen.Sie müssen nahezu die gesamte verdammte Funktion einrücken, und viel Glück dabei, sicherzustellen, dass alle Ihre if/else-Bedingungen und Klammern richtig übereinstimmen.

Diese Methode macht die Codepflege äußerst schwierig und fehleranfällig.

Strukturierte Programmierung sagt, dass Sie immer nur eine Return-Anweisung pro Funktion haben sollten.Dadurch soll die Komplexität begrenzt werden.Viele Leute wie Martin Fowler argumentieren, dass es einfacher sei, Funktionen mit mehreren Return-Anweisungen zu schreiben.Er präsentiert dieses Argument im Klassiker Umgestaltung Buch, das er geschrieben hat.Dies funktioniert gut, wenn Sie seinen anderen Ratschlägen folgen und kleine Funktionen schreiben.Ich stimme dieser Ansicht zu und nur strenge Puristen der strukturierten Programmierung halten sich an einzelne Return-Anweisungen pro Funktion.

Wie Kent Beck bei der Erörterung von Schutzklauseln feststellt Implementierungsmuster Eine Routine mit einem einzigen Ein- und Ausstiegspunkt versehen ...

"war, die mögliche Verwirrung zu verhindern, wenn es in derselben Routine in viele Orte springt.Es war sinnvoll, wenn sie auf Forran- oder Assemblersprachprogramme angewendet wurden, die mit vielen globalen Daten geschrieben wurden, bei denen selbst das Verständnis, welche Aussagen ausgeführt wurden, harte Arbeit war ...mit kleinen Methoden und überwiegend lokalen Daten ist es unnötig konservativ.“

Ich finde, dass eine mit Schutzklauseln geschriebene Funktion viel einfacher zu befolgen ist als ein langer, verschachtelter Haufen davon if then else Aussagen.

In einer Funktion, die keine Nebenwirkungen hat, gibt es keinen guten Grund, mehr als eine einzelne Rückgabe zu haben, und Sie sollten sie in einem funktionalen Stil schreiben.Bei einer Methode mit Nebenwirkungen sind die Dinge sequentieller (zeitindizierter), daher schreiben Sie in einem imperativen Stil und verwenden die Return-Anweisung als Befehl zum Stoppen der Ausführung.

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 mehrere Ebenen verschachtelter Bedingungen schreiben, gibt es wahrscheinlich eine Möglichkeit, dies umzugestalten, indem Sie beispielsweise eine Prädikatliste verwenden.Wenn Sie feststellen, dass Ihre ifs und elses syntaktisch weit voneinander entfernt sind, möchten Sie diese möglicherweise in kleinere Funktionen aufteilen.Ein bedingter Block, der sich über mehr als einen Bildschirm voller Text erstreckt, 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 in der Regel auf diese Weise schreiben.

Ich habe es in Codierungsstandards für C++ gesehen, die ein Überbleibsel von C waren: Wenn Sie nicht über RAII oder eine andere automatische Speicherverwaltung verfügen, müssen Sie bei jeder Rückgabe aufräumen, was entweder Ausschneiden und Einfügen bedeutet des Aufräumens oder eines goto (logischerweise dasselbe wie „finally“ in verwalteten Sprachen), die beide als schlechte Form gelten.Wenn Ihre Praktiken darin bestehen, intelligente Zeiger und Sammlungen in C++ oder einem anderen automatischen Speichersystem zu verwenden, gibt es dafür keinen triftigen Grund, und es dreht sich alles um die Lesbarkeit und ist eher eine Entscheidungsfrage.

Ich neige zu der Idee, dass return-Anweisungen in der Mitte der Funktion sind schlecht.Sie können Returns verwenden, um ein paar Schutzklauseln am Anfang der Funktion zu erstellen und dem Compiler natürlich ohne Probleme mitzuteilen, was er am Ende der Funktion zurückgeben soll, aber er gibt in zurück Mitte der Funktion kann leicht übersehen werden und die Interpretation der Funktion erschweren.

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 durchzusetzen.
  • Oftmals ist es hilfreich, einen Debugger-Haltepunkt für den einen Return am Ende der Funktion setzen zu können.
  • Weniger Retouren bedeuten weniger Komplexität.Linearer Code ist im Allgemeinen einfacher zu verstehen.
  • Wenn der Versuch, eine Funktion auf eine einzelne Rückgabe zu vereinfachen, zu Komplexität führt, dann ist das ein Anreiz für eine Umgestaltung auf kleinere, allgemeinere und leichter verständliche Funktionen.
  • Wenn Sie in einer Sprache ohne Destruktoren arbeiten oder RAII nicht verwenden, verringert eine einzelne Rückgabe die Anzahl der Stellen, die Sie bereinigen müssen.
  • Einige Sprachen erfordern einen einzigen Ausgangspunkt (z. B. Pascal und Eiffel).

Die Frage wird oft als falsche Dichotomie 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 Ausgangspunkt hat.

Aktualisieren:Scheinbar Die MISRA-Richtlinien fördern den Single Exit, zu.

Um es klarzustellen: Ich sage nicht, dass es so ist stets Es ist falsch, mehrere Rückgaben zu haben.Aber bei ansonsten gleichwertigen Lösungen gibt es viele gute Gründe, die Lösung mit einer einzigen Rendite zu bevorzugen.

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

Im Allgemeinen versuche ich, nur einen einzigen Ausgangspunkt für eine Funktion zu haben.Es kann jedoch vorkommen, dass dadurch tatsächlich ein komplexerer Funktionskörper als nötig entsteht. In diesem Fall ist es besser, mehrere Ausgangspunkte zu haben.Es muss wirklich eine „Beurteilung“ auf der Grundlage der resultierenden Komplexität erfolgen, aber das Ziel sollten möglichst wenige Ausstiegspunkte sein, ohne dass Komplexität und Verständlichkeit darunter leiden.

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

(Ganz abgesehen von der Tatsache, dass jede mehrzeilige Funktion in einer Sprache mit Ausnahmen ohnehin mehrere Ausstiegspunkte hat.)

Ich bevorzuge einen einzigen Ausgang, es sei denn, dies macht die Sache wirklich komplizierter.Ich habe festgestellt, dass in manchen Fällen mehrere vorhandene Punkte andere wichtigere Designprobleme verschleiern können:

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

Als ich diesen Code sah, 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 könnte es so sein

  1. Die Prüfung ist sinnlos, da sie niemals wahr ist (d. h.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 diese 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 Aufrufer geändert werden.

Es gibt zwei weitere Gründe (speziell für C++-Code, glaube ich), warum mehrere Exits tatsächlich einen haben können Negativ beeinflussen.Dabei handelt es sich um Codegröße und Compileroptimierungen.

Der Destruktor eines Nicht-POD-C++-Objekts im Gültigkeitsbereich am Ausgang einer Funktion wird aufgerufen.Bei mehreren Return-Anweisungen kann es sein, dass sich die Objekte im Gültigkeitsbereich unterscheiden und daher die Liste der aufzurufenden Destruktoren 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, lohnt es sich möglicherweise, dies zu vermeiden.

Das andere Problem betrifft „Named Return Value OptimiZation“ (auch bekannt als Copy Elision, ISO C++ '03 12.8/15).C++ ermöglicht einer Implementierung, den Aufruf des Kopierkonstruktors zu überspringen, wenn dies möglich ist:

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

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

Nimmt man einfach den Code so wie er ist, wird das Objekt „a1“ in „foo“ erstellt und dann wird sein Kopierkonstrukt aufgerufen, um „a2“ zu erstellen.Allerdings ermöglicht die Kopierelision dem Compiler, „a1“ an derselben Stelle auf dem Stapel zu erstellen wie „a2“.Es ist daher nicht erforderlich, das Objekt bei der Rückkehr der Funktion zu „kopieren“.

Mehrere Exit-Punkte erschweren die Arbeit des Compilers bei dem Versuch, dies zu erkennen, und zumindest bei einer relativ neuen Version von VC++ fand die Optimierung nicht statt, wenn der Funktionskörper mehrere Rückgaben hatte.Sehen Benannte Rückgabewertoptimierung in Visual C++ 2005 für mehr Details.

Ein einziger Ausstiegspunkt reduziert die Kosten Zyklomatische Komplexität und deshalb, in der Theorie, verringert die Wahrscheinlichkeit, dass Sie Fehler in Ihren Code einführen, wenn Sie ihn ändern.Die Praxis deutet jedoch tendenziell darauf hin, dass ein pragmatischerer Ansatz erforderlich ist.Ich tendiere daher dazu, einen einzigen Austrittspunkt zu haben, erlaube meinem Code aber auch mehrere, wenn das besser lesbar ist.

Ich zwinge mich, nur eines zu verwenden return Anweisung, da sie gewissermaßen Codegeruch erzeugen wird.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 willkürlich...)

Je mehr Bedingungen vorhanden sind, desto größer wird die Funktion und desto schwieriger ist sie zu lesen.Wenn Sie sich also auf den Code-Geruch eingestellt haben, werden Sie ihn bemerken und den Code umgestalten wollen.Zwei mögliche Lösungen sind:

  • Mehrere Rücksendungen
  • Refactoring in separate Funktionen

Mehrere Rücksendungen

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 sind gerade dabei, die Funktion auf diese Weise umzugestalten

  • eine Reihe wiederverwendbarer Funktionen erstellt,
  • machte die Funktion für Menschen lesbarer und
  • Der Fokus der Funktionen liegt darauf, warum die Werte korrekt sind.

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

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

In den meisten Fällen neige ich dazu, eine Funktion basierend auf einem logischen Pfad so schnell wie möglich zu beenden (Schutzklauseln sind ein hervorragendes Beispiel dafür).

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 programmieren wahrscheinlich nicht in C.

Es gibt kein Gesetz, das in allen Programmiersprachen nur einen Exit-Punkt für eine Methode vorschreibt.Manche Menschen bestehen auf der Überlegenheit dieses Stils und erheben ihn manchmal zu einer „Regel“ oder einem „Gesetz“, aber dieser Glaube wird durch keinerlei Beweise oder Untersuchungen gestützt.

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

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

Verwenden Sie daher so viele Rückgaben, wie es Ihrem künstlerischen Empfinden entspricht, denn es handelt sich um eine Frage des Layouts und der Lesbarkeit, nicht um eine technische.

Ich habe darüber gesprochen Dies ausführlicher auf meinem Blog.

Es gibt Gutes über einen einzigen Ausstiegspunkt, aber auch Schlechtes über das Unvermeidliche "Pfeil" Programmierung, die daraus resultiert.

Wenn ich während der Eingabevalidierung oder Ressourcenzuweisung mehrere Exit-Punkte verwende, versuche ich, alle „Fehler-Exits“ gut sichtbar oben in der Funktion zu platzieren.

Beide Spartanische Programmierung Artikel der „SSDSLPedia“ und der Einzelfunktions-Austrittspunkt Artikel im „Portland Pattern Repository's Wiki“ enthalten einige aufschlussreiche Argumente dazu.Natürlich gibt es auch diesen Beitrag zu beachten.

Wenn Sie wirklich einen einzigen Exit-Punkt (in einer beliebigen Sprache ohne Ausnahmen) wünschen, um beispielsweise Ressourcen an einem einzigen Ort freizugeben, finde ich die sorgfältige Anwendung von goto gut;siehe zum Beispiel dieses eher konstruierte Beispiel (komprimiert, um Platz auf dem Bildschirm 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 die Pfeilprogrammierung im Allgemeinen mehr als mehrere Ausstiegspunkte, obwohl beide nützlich sind, wenn sie richtig angewendet werden.Das Beste ist natürlich, Ihr Programm so zu strukturieren, dass keines von beiden erforderlich ist.Normalerweise hilft es, die Funktion in mehrere Abschnitte aufzuteilen :)

Allerdings erhalte ich dabei sowieso mehrere Austrittspunkte, wie in diesem Beispiel, wo eine größere Funktion in mehrere kleinere Funktionen zerlegt wurde:

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);
}

Je nach Projekt oder Codierungsrichtlinien könnte der Großteil des Standardcodes durch Makros ersetzt werden.Als Randbemerkung: Durch diese Aufteilung können die Funktionen g0, g1 und g2 sehr einfach einzeln getestet werden.

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

Zusamenfassend;

  • Wenige Renditen sind besser als viele Renditen
  • Mehr als eine Rendite ist besser als riesige Pfeile, und Schutzklauseln sind grundsätzlich ok.
  • Ausnahmen könnten/sollten nach Möglichkeit wahrscheinlich die meisten „Schutzklauseln“ ersetzen.

Sie kennen das Sprichwort: Schönheit liegt im Auge des Betrachters.

Manche Leute schwören darauf NetBeans und einige von IntelliJ-IDEE, einige von Python und einige von 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 danach, die Boolesche Algebra zu verwenden, um die Logik und die Verwendung von Zustandsautomaten zu reduzieren und zu vereinfachen.Allerdings gab es frühere Kollegen, die glaubten, dass mein Einsatz „mathematischer Techniken“ beim Codieren ungeeignet sei, weil er nicht sichtbar und wartbar wäre.Und das wäre eine schlechte Praxis.Tut mir leid, Leute, 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, anstatt ein Durcheinander sprichwörtlicher Spaghetti zu sehen.

Hey Kumpel (wie ein ehemaliger Kunde immer sagte): Mach, was du willst, solange du weißt, wie man es repariert, wenn ich dich brauche, um es zu reparieren.

Ich erinnere mich, dass vor 20 Jahren ein Kollege von mir entlassen wurde, weil er das eingestellt hatte, was man heute nennen würde agile Entwicklung Strategie.Er hatte einen sorgfältigen schrittweisen Plan.Aber sein Vorgesetzter schrie ihn an: „Sie können Funktionen nicht schrittweise für Benutzer freigeben!“Du musst dabei bleiben Wasserfall.“ Seine Antwort an den Manager war, dass eine inkrementelle Entwicklung präziser auf die Bedürfnisse des Kunden eingehen würde.Er glaubte daran, für die Bedürfnisse des Kunden zu entwickeln, aber der Manager glaubte an die Programmierung gemäß „Kundenanforderungen“.

Wir machen uns häufig schuldig, die Datennormalisierung gebrochen zu haben, MVP Und MVC Grenzen.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, zu versuchen, ein Regelwerk 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.

Ich neige dazu, Schutzklauseln zu verwenden, um eine Methode vorzeitig zurückzugeben und andernfalls am Ende einer Methode zu beenden.Die Single-Entry- und Exit-Regel hat historische Bedeutung und war besonders hilfreich beim Umgang mit Legacy-Code, der für eine einzelne C++-Methode 10 A4-Seiten mit mehreren Rückgaben (und vielen Fehlern) umfasste.In jüngerer Zeit ist es eine anerkannte bewährte Praxis, die Methoden klein zu halten, wodurch mehrere Ausgänge das Verständnis weniger behindern.Im folgenden von oben kopierten Kronoz-Beispiel geht es um die Frage, was in vorkommt //Rest des Codes...?:

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 gekünstelt ist, aber ich wäre versucht, es umzugestalten für jede Schleife in eine LINQ-Anweisung, die dann als Schutzklausel betrachtet werden könnte.Auch hier ist in einem erfundenen Beispiel die Absicht des Codes nicht offensichtlich und someFunction() kann eine andere Nebenwirkung haben oder das Ergebnis kann in der Anwendung verwendet werden // Rest des Codes....

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

Geben Sie die folgende überarbeitete Funktion an:

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;
}

Ein guter Grund, der mir einfällt, ist die Codepflege: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 auch einfach einen Haltepunkt setzen :)

Allerdings musste ich einmal in einer Bibliothek arbeiten, in der die Codierungsstandards „eine Return-Anweisung pro Funktion“ vorschrieben, und ich fand das ziemlich schwierig.Ich schreibe viel Code für numerische Berechnungen und es gibt oft „Sonderfälle“, sodass der Code am Ende ziemlich schwer zu befolgen ist ...

Für Funktionen, die klein genug sind, sind mehrere Ausstiegspunkte in Ordnung – also eine Funktion, die in ihrer Gesamtheit auf einer Bildschirmlänge angezeigt werden kann.Wenn eine lange Funktion ebenfalls mehrere Ausstiegspunkte enthält, ist dies ein Zeichen dafür, dass die Funktion weiter zerstückelt werden kann.

Allerdings vermeide ich Multiple-Exit-Funktionen es sei denn, es ist absolut notwendig.Ich habe Probleme mit Fehlern, die auf eine verirrte Rückkehr in einer unklaren Zeile in komplexeren Funktionen zurückzuführen sind.

Ich habe mit schrecklichen Codierungsstandards gearbeitet, die einem einen einzigen Ausstiegspfad aufzwingen, und das Ergebnis sind fast immer unstrukturierte Spaghetti, wenn die Funktion alles andere als trivial ist – am Ende kommt es zu vielen Unterbrechungen und Fortsetzungen, die nur im Weg stehen.

Ein einziger Exit-Punkt – alle anderen Bedingungen gleich – macht den Code deutlich lesbarer.Aber da ist ein Fang:beliebte Konstruktion

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

ist eine Fälschung, „res=" ist nicht viel besser als „return".Es gibt 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, sie in mehrere kleinere Funktionen mit einem einzigen Austrittspunkt aufzuteilen.

Meine übliche Richtlinie besteht darin, am Ende einer Funktion nur eine Return-Anweisung zu haben, es sei denn, die Komplexität des Codes wird durch das 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 gemacht werden kann, als es die offensichtliche Version ohne sie wäre.Man könnte argumentieren, dass mehr Überarbeitung erforderlich ist, wenn Sie eine Funktion haben, die zu komplex ist, um ohne mehrere Rückgabeanweisungen verständlich zu sein, aber manchmal ist es gut, bei solchen Dingen pragmatisch vorzugehen.

Wenn Sie am Ende mehr als ein paar Rückgaben erhalten, stimmt möglicherweise etwas mit Ihrem Code nicht.Ansonsten würde ich zustimmen, dass es manchmal schön ist, von mehreren Stellen in einer Unterroutine zurückkehren zu können, insbesondere wenn der Code dadurch 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 war

Als Richtlinie stimme ich am Ende für die einmalige Rückgabe.Das hilft einem Allgemeine Code-Bereinigungsbehandlung ...Schauen Sie sich zum Beispiel 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);
   }
}

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

Während die Probleme mit dem „Pfeilcode“ völlig richtig sind, scheint ein Problem bei der Verwendung mehrerer Return-Anweisungen zu verschwinden, wenn Sie einen Debugger verwenden.Sie haben keine praktische Catch-All-Position, um einen Haltepunkt zu setzen, um sicherzustellen, dass Sie den Ausgang und damit die Rückgabebedingung sehen.

Je mehr Rückgabeanweisungen Sie in einer Funktion haben, desto höher ist die Komplexität dieser einen Methode.Wenn Sie sich fragen, ob Sie zu viele Rückgabeanweisungen haben, möchten Sie sich vielleicht fragen, ob diese Funktion zu viele Codezeilen enthält.

Aber nicht, es ist nichts Falsches an einer oder mehreren Rückgabeanweisungen.In einigen Sprachen ist dies eine bessere Vorgehensweise (C++) als in anderen (C).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top