779 Stimmen

Sollte eine Funktion nur eine Rückgabeanweisung haben?

Gibt es gute Gründe, warum es besser ist, nur eine Rückgabeanweisung in einer Funktion zu haben?

Oder ist es in Ordnung, aus einer Funktion zurückzukehren, sobald dies logisch korrekt ist, d.h. es kann viele Return-Anweisungen in der Funktion geben?

25 Stimmen

Ich stimme nicht zu, dass die Frage sprachenfeindlich ist. Bei einigen Sprachen ist die Mehrfachrückgabe natürlicher und bequemer 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.

3 Stimmen

Diese Frage ist eng damit verbunden und enthält ausgezeichnete Antworten: programmers.stackexchange.com/questions/118703/

0 Stimmen

Sprachunabhängig? Erklären Sie jemandem, der eine funktionale Sprache verwendet, dass er einen Return pro Funktion verwenden muss :p

11voto

Jrgns Punkte 23690

Ich zwinge mich, nur einen einzigen zu benutzen return Anweisung, da sie in gewisser Weise Codegeruch erzeugt. Lassen Sie mich das 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 sehr prominent...)

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

  • Mehrfachrückgaben
  • Refaktorierung 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;
}

Getrennte 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 unübersichtlich, aber bei der Umstrukturierung der Funktion auf diese Weise haben wir

  • eine Reihe von wiederverwendbaren Funktionen geschaffen,
  • die Funktion besser lesbar gemacht, und
  • Der Schwerpunkt der Funktionen liegt darauf, warum die Werte richtig sind.

5 Stimmen

-1: Schlechtes Beispiel. Sie haben die Behandlung der Fehlermeldung weggelassen. Wenn das nicht nötig wäre, könnte isCorrect einfach als return xx && yy && zz; ausgedrückt werden, wobei xx, yy und z die Ausdrücke isEqual, isDouble und isThird sind.

10voto

Henrik Gustafsson Punkte 46490

Es gibt gute Argumente für einen einzigen Ausstiegspunkt, genauso wie es schlechte Argumente für den unvermeidlichen Ausstiegspunkt gibt. "Pfeil" die daraus resultierende Programmierung.

Wenn bei der Eingabevalidierung oder der Ressourcenzuweisung mehrere Exit-Points verwendet werden, versuche ich, alle "Fehler-Exits" gut sichtbar an den Anfang der Funktion zu stellen.

Sowohl die Spartanische Programmierung Artikel der "SSDSLPedia" und der einzelne Funktionsausstiegspunkt Artikel des "Portland Pattern Repository's Wiki" haben einige aufschlussreiche Argumente zu diesem Thema. Außerdem gibt es natürlich diesen Beitrag zu berücksichtigen.

Wenn man wirklich einen einzigen Exit-Point haben möchte (in jeder nicht-ausnahmefähigen Sprache), um z.B. Ressourcen an einer einzigen Stelle freizugeben, finde ich die vorsichtige Anwendung von goto gut; siehe z.B. dieses ziemlich konstruierte 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;
}

Ich persönlich mag die Pfeilprogrammierung im Allgemeinen nicht so sehr wie mehrere Ausstiegspunkte, obwohl beide nützlich sind, wenn sie richtig angewendet werden. Am besten ist es natürlich, wenn Sie Ihr Programm so strukturieren, dass beides nicht erforderlich ist. Die Aufteilung einer Funktion in mehrere Teile ist in der Regel hilfreich :)

Obwohl ich dabei feststelle, dass ich ohnehin mehrere Ausstiegspunkte habe, wie in diesem Beispiel, wo eine größere Funktion in mehrere kleinere Funktionen aufgeteilt 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 Kodierungsrichtlinien könnte der meiste Standardcode durch Makros ersetzt werden. Nebenbei bemerkt: Durch diese Aufteilung lassen sich die Funktionen g0, g1 und g2 sehr leicht einzeln testen.

In einer OO- und ausnahmefähigen Sprache würde ich solche if-Anweisungen natürlich nicht verwenden (oder überhaupt nicht, wenn ich mit geringem Aufwand damit auskommen könnte), und der Code wäre viel einfacher. Und nicht pfeilartig. Und die meisten der nicht endgültigen Rückgaben wären wahrscheinlich Ausnahmen.

Kurz gesagt;

  • Wenige Erträge sind besser als viele Erträge
  • Mehr als eine Rückkehr 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.

0 Stimmen

Das Beispiel stürzt bei y < 0 ab, weil es versucht, den NULL-Zeiger freizugeben ;-)

2 Stimmen

opengroup.org/onlinepubs/009695399/functions/free.html "Wenn ptr ein Null-Zeiger ist, wird keine Aktion ausgeführt."

1 Stimmen

Nein, es wird nicht abstürzen, weil die Übergabe von NULL an free ein definiertes No-op ist. Es ist ein ärgerlicherweise weit verbreiteter Irrglaube, dass man zuerst auf NULL testen muss.

10voto

Rob Cooper Punkte 28132

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

Ich persönlich habe noch nie gehört/gesehen, dass eine "Best Practice" besagt, dass man nur eine Return-Anweisung haben sollte.

In den meisten Fällen neige ich dazu, eine Funktion so schnell wie möglich auf der Grundlage eines logischen Pfades zu beenden (guard-Klauseln sind ein hervorragendes Beispiel dafür).

10voto

Anthony Punkte 5038

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

Es gibt kein Gesetz, das vorschreibt, dass es in allen Programmiersprachen nur einen Exit-Point für eine Methode gibt. . Einige Leute 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 mag eine schlechte Angewohnheit in C-Code sein, wo Ressourcen explizit freigegeben werden müssen, aber Sprachen wie Java, C#, Python oder JavaScript, die über Konstrukte wie automatische Garbage Collection und 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 zentrale manuelle Ressourcendeallokation erforderlich ist.

Es gibt Fälle, in denen ein einzelner Rücklauf besser lesbar ist, und Fälle, in denen dies nicht der Fall ist. Prüfen Sie, ob sich dadurch die Anzahl der Codezeilen verringert, die Logik klarer wird oder die Anzahl der geschweiften Klammern und Einzüge oder temporären Variablen reduziert wird.

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

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

9voto

David Clarke Punkte 12420

Ich neige dazu, Guard-Klauseln zu verwenden, um früh zurückzukehren und ansonsten am Ende einer Methode zu beenden. Die Regel des einmaligen Ein- und Ausstiegs hat historische Bedeutung und war besonders hilfreich, als es um alten Code ging, der für eine einzige C++-Methode mit mehreren Rückgaben (und vielen Fehlern) 10 A4-Seiten umfasste. In jüngerer Zeit hat es sich bewährt, die Methoden klein zu halten, so dass mehrere Ausgänge weniger ein Hindernis für das Verständnis darstellen. In dem folgenden Kronoz-Beispiel, das wir oben kopiert haben, geht es um die Frage, was in //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;
}

Ich weiß, dass das Beispiel etwas konstruiert ist, aber ich wäre versucht, die foreach Schleife in eine LINQ-Anweisung, die dann als Schutzklausel betrachtet werden könnte. Auch hier ist die Absicht des Codes in einem erfundenen Beispiel nicht ersichtlich und someFunction() kann eine andere Nebenwirkung haben oder das Ergebnis kann in der Praxis 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:

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

0 Stimmen

Gibt es in C++ keine Ausnahmen? Warum würden Sie dann null anstatt eine Ausnahme zu machen, die anzeigt, dass das Argument nicht akzeptiert wird?

1 Stimmen

Wie ich bereits angedeutet habe, ist der Beispielcode aus einer früheren Antwort kopiert ( stackoverflow.com/a/36729/132599 ). Das ursprüngliche Beispiel gab Nullen zurück, und die Umstrukturierung zum Auslösen von Argumentausnahmen war für den Punkt, den ich zu machen versuchte, oder für die ursprüngliche Frage nicht wesentlich. Als eine Frage der guten Praxis, dann ja würde ich normalerweise (in C#) werfen ein ArgumentNullException in einer guard-Klausel statt einen Nullwert zurück.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X