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

33voto

Mark Punkte 41

Ein einziger Exit-Point bietet einen Vorteil bei der Fehlersuche, da Sie so einen einzigen Haltepunkt am Ende einer Funktion setzen können, um zu sehen, welcher Wert tatsächlich zurückgegeben wird.

6 Stimmen

BRAVO! Sie sind der sólo Person, die dies erwähnt Zielsetzung Grund. Das ist der Grund, warum ich einzelne Exit-Points gegenüber mehreren Exit-Points bevorzuge. Wenn mein Debugger einen Haltepunkt setzen könnte bei cualquier Ausstiegspunkt Ich würde wahrscheinlich mehrere Ausstiegspunkte bevorzugen. Meine derzeitige Meinung ist, dass die Leute, die mehrere Exit-Points programmieren, dies zu ihrem eigenen Vorteil tun, auf Kosten der anderen, die einen Debugger für ihren Code verwenden müssen (und ja, ich spreche von all den Open-Source-Mitwirkenden, die Code mit mehreren Exit-Points schreiben).

3 Stimmen

YES. Ich füge Protokollierungscode zu einem System hinzu, das in der Produktion (wo ich nicht durchgehen kann) zeitweise fehlerhaft ist. Wenn der vorherige Programmierer Single-Exit verwendet hatte, wäre es viel einfacher gewesen.

3 Stimmen

Stimmt, bei der Fehlersuche ist es hilfreich. Aber in der Praxis konnte ich in den meisten Fällen den Haltepunkt in der aufrufenden Funktion setzen, direkt nach dem Aufruf - mit demselben Ergebnis. (Und diese Position befindet sich natürlich auf dem Aufrufstapel.) YMMV.

19voto

Scott Dorman Punkte 41206

Im Allgemeinen versuche ich, nur einen einzigen Ausstiegspunkt aus einer Funktion zu haben. Es gibt jedoch Fälle, in denen dies dazu führt, dass der Funktionskörper komplexer wird als nötig, dann ist es besser, mehrere Ausstiegspunkte zu haben. In diesem Fall ist es besser, mehrere Exit-Points zu haben. Es ist wirklich eine "Ermessensentscheidung", die auf der resultierenden Komplexität basiert, aber das Ziel sollte sein, so wenig Exit-Points wie möglich zu haben, ohne die Komplexität und Verständlichkeit zu opfern.

0 Stimmen

"Im Allgemeinen versuche ich, nur einen einzigen Ausstiegspunkt aus einer Funktion zu haben" - warum? "Das Ziel sollte sein, so wenig Ausstiegspunkte wie möglich zu haben" - warum? Und warum haben 19 Leute für diese Nicht-Antwort gestimmt?

0 Stimmen

@JimBalter Letztlich ist es eine Frage der persönlichen Vorlieben. Mehr Ausstiegspunkte typischerweise zu einer komplexeren Methode führen (wenn auch nicht immer) und das Verständnis erschweren.

0 Stimmen

"Es ist eine Frage der persönlichen Vorlieben. "Mit anderen Worten: Sie können keinen Grund nennen. "Mehr Exit-Points führen typischerweise zu einer komplexeren Methode (wenn auch nicht immer) " -- Nein, eigentlich nicht. Bei zwei logisch äquivalenten Funktionen, eine mit Guard-Klauseln und eine mit einem einzigen Exit, wird letztere eine höhere zyklomatische Komplexität aufweisen, was zahlreichen Studien zufolge zu einem fehleranfälligeren und schwer verständlichen Code führt. Es wäre von Vorteil, die anderen Antworten hier zu lesen.

15voto

Ben Lings Punkte 28080

Nein, denn wir leben nicht mehr in den 1970er Jahren . Wenn Ihre Funktion so lang 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 Exit-Points hat).

14voto

Richard Corden Punkte 20939

Ich würde einen einzigen Ausgang bevorzugen, es sei denn, es macht die Sache wirklich kompliziert. Ich habe die Erfahrung gemacht, dass mehrere Ausgänge in manchen Fällen andere, wichtigere Designprobleme überdecken 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?

Je nach den Antworten auf diese Fragen könnte es sein, dass

  1. die Prüfung ist sinnlos, da sie nie wahr ist (d.h. sie sollte eine Behauptung sein)
  2. die Prüfung ist sehr selten wahr und es ist daher vielleicht besser, diese speziellen Aufruferfunktionen zu ändern, da sie wahrscheinlich ohnehin eine andere Aktion durchführen sollten.

In beiden oben genannten Fällen kann der Code wahrscheinlich mit einer Assertion überarbeitet werden, um sicherzustellen, dass "foo" niemals null ist, und die entsprechenden Aufrufer geändert werden.

Es gibt zwei weitere Gründe (spezifisch für C++-Code, denke ich), bei denen mehrere Existenzen tatsächlich eine negativ beeinflussen. Das sind die Codegröße und die Compiler-Optimierungen.

Bei einem Nicht-POD-C++-Objekt, das sich beim Verlassen einer Funktion im Anwendungsbereich befindet, wird sein Destruktor aufgerufen. Bei mehreren Rückgabeanweisungen kann es vorkommen, dass sich verschiedene Objekte im Anwendungsbereich befinden und somit die Liste der aufzurufenden Destruktoren unterschiedlich ist. Der Compiler muss daher für jede Rückgabeanweisung Code erzeugen:

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 Größe des Codes eine Rolle spielt, sollte man dies vielleicht vermeiden.

Das andere Problem bezieht sich auf die "Named Return Value OptimiZation" (auch bekannt als Copy Elision, ISO C++ '03 12.8/15). C++ erlaubt es einer Implementierung, den Aufruf des Kopierkonstruktors zu überspringen, wenn sie es kann:

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. Die Copy Elision erlaubt es dem Compiler jedoch, 'a1' an der gleichen Stelle auf dem Stack zu konstruieren wie 'a2'. Es besteht daher keine Notwendigkeit, das Objekt zu "kopieren", wenn die Funktion zurückkehrt.

Mehrere Exit-Points 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. Siehe Optimierung von benannten Rückgabewerten in Visual C++ 2005 für weitere Einzelheiten.

1 Stimmen

Wenn Sie alle bis auf den letzten dtor aus Ihrem C++-Beispiel herausnehmen, muss der Code für die Zerstörung von B und später von C und B immer noch generiert werden, wenn der Geltungsbereich der if-Anweisung endet, so dass Sie wirklich nichts gewinnen, wenn Sie keine mehrfachen Rückgaben haben.

4 Stimmen

+1 Und ganz unten auf der Liste finden wir der ECHTE Grund für die Existenz dieser Kodierungspraxis - NRVO. Allerdings handelt es sich hierbei um eine Mikro-Optimierung; und wie alle Mikro-Optimierungspraktiken wurde sie wahrscheinlich von einem 50 Jahre alten "Experten" begonnen, der es gewohnt ist, auf einer 300 kHz PDP-8 zu programmieren, und der die Bedeutung von sauberem und strukturiertem Code nicht versteht. Im Allgemeinen sollten Sie den Rat von Chris S. befolgen und mehrere Rücksprunganweisungen verwenden, wann immer dies sinnvoll ist.

0 Stimmen

Während ich mit Ihrer Präferenz nicht einverstanden bin (meiner Meinung nach ist Ihr Assert-Vorschlag auch ein Rückgabepunkt, ebenso wie ein throw new ArgumentNullException() in C# in diesem Fall), Ich mochte wirklich Ihre anderen Überlegungen, sie sind alle gültig zu mir, und könnte in einigen Nischenkontexten kritisch sein.

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.

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