Bewährte Vorgehensweise bei der Rückkehr


60

Ich möchte wissen, was als bessere Art der Rückgabe angesehen wird, wenn ich eine ifErklärung habe.

Beispiel 1:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }
   else
   {
      myString = "Name " + myString;
      // Do something more here...
      return true;
   }
}

Beispiel 2:

public bool MyFunction()
{
   // Get some string for this example
   string myString = GetString();

   if (myString == null)
   {
      return false;
   }

   myString = "Name " + myString;
   // Do something more here...
   return true;
}

Wie Sie in beiden Beispielen sehen können, wird die Funktion zurückgegeben. true/falseIst es jedoch eine gute Idee, die elseAnweisung wie im ersten Beispiel zu platzieren, oder ist es besser, sie nicht zu platzieren?


7
Wenn Sie nur Fehler im ersten "Wenn" prüfen, schließen Sie "Sonst" besser nicht ein, da Fehler nicht als Teil der eigentlichen Logik betrachtet werden sollten.
Mert Akcakaya

2
persönlich würde ich mir mehr sorgen machen, dass die funktion nebeneffekte verursacht, aber ich denke, das ist nur ein schlecht gewähltes beispiel?
jk.


14
Für mich ist die erste Version so, als würden Sie alle Schüler im Raum entlassen, die ihre Hausaufgaben nicht erledigt haben, und dann, wenn sie gegangen sind, zu den anderen Schülern sagen: "Wenn Sie Ihre Hausaufgaben erledigt haben ...". Es macht Sinn, ist aber unnötig. Da die Bedingung nicht mehr wirklich eine Bedingung ist, neige ich dazu, die zu verwerfen else.
Nicole

1
Was ist das "hier mehr tun", das Sie tun möchten? Das könnte die Gestaltung Ihrer Funktion grundlegend verändern.
Benedikt

Antworten:


81

Beispiel 2 ist als Schutzblock bekannt. Es ist besser geeignet, eine Ausnahme frühzeitig zurückzugeben / auszulösen, wenn ein Fehler aufgetreten ist (falscher Parameter oder ungültiger Zustand). Im normalen logischen Ablauf ist es besser, Beispiel 1 zu verwenden


+1 - eine gute Unterscheidung, und was ich antworten wollte.
Telastyn,

3
@ pR0Ps hat die gleiche Antwort und liefert ein Codebeispiel dafür, warum es letztendlich ein sauberer Weg ist, dem man folgen muss. +1 für die gute Antwort.

Diese Frage wurde schon oft auf SO gestellt und obwohl es umstritten sein kann, ist dies eine gute Antwort. Viele Menschen, die darauf trainiert wurden, erst am Ende zurückzukehren, haben einige Probleme mit diesem Konzept.
Bill K

2
+1. Ein unsachgemäßes Vermeiden von Schutzblöcken kann zu Pfeilcode führen.
Brian

Zeigen nicht beide Beispiele einen Schutzblock?
ernie

44

Mein persönlicher Stil ist es, die Single iffür Guard Blocks und den if/ elseim eigentlichen Methodenverarbeitungscode zu verwenden.

In diesem Fall verwenden Sie die myString == nullals Schutzbedingung, daher würde ich eher das einzelne ifMuster verwenden.

Betrachten Sie Code, der etwas komplizierter ist:

Beispiel 1:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }
    else{
        //processing block
        myString = escapedString(myString);

        if (myString == "foo"){
            //some processing here
            return false;
        }
        else{
            myString = "Name " + myString;
            //other stuff
            return true;
        }
    }
}

Beispiel 2:

public bool MyFunction(myString: string){

    //guard block
    if (myString == null){
        return false;
    }

    //processing block
    myString = escapedString(myString);

    if (myString == "foo"){
        //some processing here
        return false;
    }
    else{
        myString = "Name " + myString;
        //other stuff
        return true;
    }
}

In Beispiel 1 haben sowohl der Guard als auch der Rest der Methode die Form if/ else. Vergleichen Sie dies mit Beispiel 2, in dem sich der Schutzblock in der einzelnen ifForm befindet, während der Rest der Methode die Form if/ elseverwendet. Ich persönlich finde Beispiel 2 einfacher zu verstehen, während Beispiel 1 unsauber und stark eingerückt aussieht.

Beachten Sie, dass dies ein erfundenes Beispiel ist und dass Sie else ifAnweisungen verwenden können, um es zu bereinigen, aber ich möchte den Unterschied zwischen Schutzblöcken und dem tatsächlichen Funktionsverarbeitungscode aufzeigen.

Ein anständiger Compiler sollte sowieso für beide die gleiche Ausgabe erzeugen. Der einzige Grund, den einen oder anderen zu verwenden, ist die persönliche Präferenz oder die Anpassung an den Stil des vorhandenen Codes.


18
Beispiel 2 wäre noch einfacher zu lesen, wenn Sie die Sekunde sonst los würden.
Briddums

18

persönlich bevorzuge ich die zweite Methode. Ich habe das Gefühl, es ist kürzer, hat weniger Einrückungen und ist leichter zu lesen.


+1 für weniger Einrückung. Dies ist offensichtlich, wenn Sie mehrere Schecks haben
d.raev

15

Meine persönliche Praxis ist die folgende:

  • Ich mag keine Funktionen mit mehreren Exit-Punkten, ich fand es schwierig, sie zu warten und zu befolgen. Code-Änderungen brechen manchmal die interne Logik, weil sie von Natur aus etwas schlampig sind. Wenn es sich um eine komplexe Berechnung handelt, erstelle ich zu Beginn einen Rückgabewert und gebe ihn am Ende zurück. Dies zwingt mich, die einzelnen If-else-, Switch- usw. Pfade sorgfältig zu befolgen und den Wert an den richtigen Stellen richtig einzustellen. Ich verbringe auch ein bisschen Zeit damit, zu entscheiden, ob ein Standardrückgabewert festgelegt oder beim Start nicht initialisiert werden soll. Diese Methode hilft auch, wenn sich die Logik oder der Rückgabewerttyp oder die Bedeutung ändert.

Zum Beispiel:

public bool myFunction()
{
   // First parameter loading
   String myString = getString();

   // Location of "quick exits", see the second example
   // ...

   // declaration of external resources that MUST be released whatever happens
   // ...

   // the return variable (should think about giving it a default value or not) 
   // if you have no default value, declare it final! You will get compiler 
   // error when you try to set it multiple times or leave uninitialized!
   bool didSomething = false;

   try {
     if (myString != null)
     {
       myString = "Name " + myString;
       // Do something more here...

       didSomething = true;
     } else {
       // get other parameters and data
       if ( other conditions apply ) {
         // do something else
         didSomething = true;
       }
     }

     // Edit: previously forgot the most important advantage of this version
     // *** HOUSEKEEPING!!! ***

   } finally {

     // this is the common place to release all resources, reset all state variables

     // Yes, if you use try-finally, you will get here from any internal returns too.
     // As I said, it is only my taste that I like to have one, straightforward path 
     // leading here, and this works even if you don't use the try-finally version.

   }

   return didSomething;
}
  • Einzige Ausnahme: "Quick Exit" beim Start (oder in seltenen Fällen innerhalb des Prozesses). Wenn die reale Berechnungslogik eine bestimmte Kombination von Eingabeparametern und internen Zuständen nicht verarbeiten kann oder eine einfache Lösung ohne Ausführen des Algorithmus bietet, hilft es nicht, den gesamten Code in (manchmal tiefen) if-Blöcken zu kapseln. Dies ist ein "Ausnahmezustand", der nicht Teil der Kernlogik ist. Ich muss also aus der Berechnung aussteigen, sobald ich ihn entdeckt habe. In diesem Fall gibt es keine else-Verzweigung, im Normalfall wird die Ausführung einfach fortgesetzt. (Natürlich wird "Ausnahmezustand" besser durch das Auslösen einer Ausnahme ausgedrückt, aber manchmal ist es ein Overkill.)

Zum Beispiel:

public bool myFunction()
{
   String myString = getString();

   if (null == myString)
   {
     // there is nothing to do if myString is null
     return false;
   } 

   myString = "Name " + myString;
   // Do something more here...

   // not using return value variable now, because the operation is straightforward.
   // if the operation is complex, use the variable as the previous example.

   return true;
}

Die "one exit" -Regel hilft auch, wenn die Berechnung externe Ressourcen erfordert, die Sie freigeben müssen, oder wenn Sie vor dem Verlassen der Funktion einen Reset durchführen müssen. manchmal werden sie später während der Entwicklung hinzugefügt. Bei mehreren Ausgängen im Algorithmus ist es viel schwieriger, alle Zweige ordnungsgemäß zu erweitern. (Und wenn Ausnahmen auftreten, sollte die Freigabe / Rücksetzung ebenfalls in einen Endblock gestellt werden, um in seltenen Ausnahmefällen Nebenwirkungen zu vermeiden ...).

Ihr Fall scheint in die Kategorie "Quick Exit Before Real Work" zu fallen, und ich würde es so schreiben wie Ihre Version in Beispiel 2.


9
+1 für "Ich mag keine Funktionen mit mehreren Austrittspunkten"
Corv1nus

@LorandKedves - ein Beispiel hinzugefügt - hoffe, es macht Ihnen nichts aus.
Matthew Flynn

@MatthewFlynn naja, wenn es dir nichts ausmacht, dass es dem widerspricht, was ich am Ende meiner Antwort vorgeschlagen habe ;-) Ich setze das Beispiel fort und hoffe, dass es endlich für uns beide gut wird :-)
Lorand Kedves

@MatthewFlynn (Entschuldigung, ich bin nur ein mürrischer Bastard und danke, dass Sie mir geholfen haben, meine Meinung zu klären. Ich hoffe, Ihnen gefällt die aktuelle Version.)
Lorand Kedves

13
Von Funktionen mit mehreren Austrittspunkten und Funktionen mit einer veränderlichen Ergebnisvariablen scheint mir die erstere bei weitem das geringere von zwei Übeln zu sein.
Jon Purdy

9

Ich bevorzuge es aus zwei Gründen, Schutzblöcke einzusetzen, wo immer ich kann:

  1. Sie ermöglichen ein schnelles Verlassen unter bestimmten Bedingungen.
  2. Das beseitigt die Notwendigkeit für komplexe und unnötige if-Anweisungen später im Code.

Im Allgemeinen bevorzuge ich Methoden, bei denen die Kernfunktionalität der Methode klar und minimal ist. Schutzblöcke helfen dabei, dies visuell umzusetzen.


5

Ich mag den "Fall Through" -Ansatz:

public bool MyFunction()
{
   string myString = GetString();

   if (myString != null)
   {
     myString = "Name " + myString;
     return true;
    }
    return false;
}

Die Aktion hat eine bestimmte Bedingung, alles andere ist nur die Standardrückgabe "fall through".


1

Wenn ich eine Single-If-Bedingung hätte, würde ich nicht zu viel Zeit damit verbringen, über den Stil nachzudenken. Aber wenn ich mehrere Schutzbedingungen habe, würde ich style2 vorziehen

Picuture dies. Angenommen, die Tests sind komplex und Sie möchten sie nicht wirklich in eine einzelne Bedingung mit ODER verknüpfen, um Komplexität zu vermeiden:

//Style1
if (this1 != Right)
{ 
    return;
}
else if(this2 != right2)
{
    return;
}
else if(this3 != right2)
{
    return;
}
else
{
    //everything is right
    //do something
    return;
}

gegen

//Style 2
if (this1 != Right)
{ 
   return;
}
if(this2 != right2)
{
    return;
}
if(this3 != right2)
{
    return;
}


//everything is right
//do something
return;

Hier gibt es zwei Hauptvorteile

  1. Sie teilen den Code in einer einzigen Funktion in zwei visuell logische Blöcke auf: einen oberen Block mit Überprüfungen (Schutzbedingungen) und einen unteren Block mit ausführbarem Code.

  2. Wenn Sie eine Bedingung hinzufügen / entfernen müssen, verringern Sie die Wahrscheinlichkeit, dass die gesamte if-elseif-else-Leiter durcheinander gebracht wird.

Ein weiterer kleiner Vorteil ist, dass Sie weniger Zahnspangensätze benötigen.


0

Das sollte eine Frage sein, die sich besser anhört.

If condition
  do something
else
  do somethingelse

drückt sich dann besser aus

if condition
  do something
do somethingelse

Für kleine Methoden ist es kein großer Unterschied, aber für größere zusammengesetzte Methoden kann es schwieriger zu verstehen sein, da es nicht richtig getrennt werden kann


3
Wenn eine Methode so lang ist, dass die zweite Form schwer zu verstehen ist, ist sie zu lang.
Kevin Cline

7
Ihre beiden Fälle sind nur dann gleichwertig, wenn sie do somethingeine Rücksendung beinhalten. Ist dies nicht der Fall, wird der zweite Code do somethingelseunabhängig vom ifPrädikat ausgeführt. Dies ist nicht die Funktionsweise des ersten Blocks.
Adnan

Dies erklärte auch das OP in seinen Beispielen. Einfach der Fragestellung folgen
José Valente

0

Ich finde Beispiel 1 irritierend, weil die fehlende return-Anweisung am Ende einer value-return-Funktion sofort das "Wait, there there something wrong" -Flag auslöst. In solchen Fällen würde ich also mit Beispiel 2 fortfahren.

In der Regel handelt es sich jedoch um mehr, je nach Zweck der Funktion, z. B. Fehlerbehandlung, Protokollierung usw. Ich bin also mit Lorand Kedves der Antwort auf diese Frage und habe am Ende in der Regel einen Ausstiegspunkt, auch auf Kosten einer zusätzlichen Flagvariablen. Dies erleichtert in den meisten Fällen die Wartung und spätere Erweiterungen.


0

Wenn Ihre ifAnweisung immer zurückkehrt, gibt es keinen Grund, für den verbleibenden Code in der Funktion ein else zu verwenden. Dadurch werden zusätzliche Zeilen und zusätzliche Einrückungen hinzugefügt. Das Hinzufügen von unnötigem Code erschwert das Lesen, und wie jeder weiß, ist das Lesen von Code schwierig .


0

In Ihrem Beispiel ist das elsenatürlich unnötig, ABER ...

Wenn Sie schnell durch Codezeilen blättern, schauen Sie in der Regel auf die geschweiften Klammern und Einrückungen, um eine Vorstellung vom Code-Fluss zu erhalten. bevor sie sich tatsächlich auf den Code selbst festlegen. Wenn Sie also elseden zweiten Codeblock mit geschweiften Klammern und Einrückungen versehen, kann der Leser schneller erkennen, dass es sich um eine A- oder B-Situation handelt, in der der eine oder der andere Block ausgeführt wird. Das heißt, der Leser sieht die geschweiften Klammern und Einrückungen, BEVOR sie die returnnach dem Anfangsbuchstaben sehen if.

Meiner Meinung nach ist dies einer der seltenen Fälle, in denen das Hinzufügen eines redundanten Codes die Lesbarkeit des Codes erhöht und nicht verringert.


0

Ich würde Beispiel 2 vorziehen, da es sofort offensichtlich ist, dass die Funktion etwas zurückgibt . Aber noch mehr, ich würde es vorziehen, von einem Ort zurückzukehren, so:

public bool MyFunction()
{
    bool result = false;

    string myString = GetString();

    if (myString != nil) {
        myString = "Name " + myString;

        result = true;
    }

    return result;
}

Mit diesem Stil kann ich:

  1. Sieh sofort, dass ich etwas zurückgebe.

  2. Fangen Sie das Ergebnis jedes Funktionsaufrufs mit nur einem Haltepunkt ab.


Dies ist ähnlich wie ich das Problem angehen würde. Der einzige Unterschied ist, dass ich die Ergebnisvariable verwenden würde, um die myString-Auswertung zu erfassen und als Rückgabewert zu verwenden.
Chuck Conway

@ChuckConway Einverstanden, aber ich habe versucht, mich an den Prototyp des OP zu halten.
Caleb,

0

Mein persönlicher Stil tendiert dazu zu gehen

function doQuery(string) {
    if (!query(string)) {
        query("ABORT");
        return false;
    } // else
    if(!anotherquery(string) {
        query("ABORT");
        return false;
    } // else
    return true;
}

Verwenden Sie die auskommentierten elseAnweisungen, um den Programmablauf anzuzeigen und lesbar zu halten. Vermeiden Sie jedoch massive Einrückungen, die bei vielen Schritten leicht über den Bildschirm gelangen können.


1
Persönlich hoffe ich, dass ich nie mit Ihrem Code arbeiten muss.
ein Lebenslauf

Wenn Sie mehrere Prüfungen haben, kann diese Methode etwas lang werden. Die Rückkehr aus jeder if-Klausel ähnelt der Verwendung der goto-Anweisung zum Überspringen von Codeabschnitten.
Chuck Conway

Wenn es eine Wahl zwischen diesem und Code mit den eingeschlossenen elseKlauseln wäre, würde ich letzteren wählen, weil der Compiler sie auch effektiv ignorieren wird. Obwohl es auf den abhängen würde else if nicht die Erhöhung der Vertiefung mehr als das Original einmal - wenn es tat würde ich Ihnen zustimmen. Aber meine Wahl wäre es, die elsekomplett fallen zu lassen, wenn dieser Stil erlaubt wäre.
Mark Hurd

0

ich würde schreiben

public bool SetUserId(int id)
{
   // Get user name from id
   string userName = GetNameById(id);

   if (userName != null)
   {
       // update local ID and userName
       _id = id;
       _userNameLabelText = "Name: " + userName;
   }

   return userName != null;
}

Ich bevorzuge diesen Stil, weil es am offensichtlichsten ist, dass ich zurückkehren würde userName != null.


1
Die Frage ist, warum bevorzugen Sie diesen Stil?
Caleb

... Ehrlich gesagt bin ich mir nicht sicher, warum Sie in diesem Fall überhaupt die Zeichenfolge wollen, ganz zu schweigen davon, warum Sie am Ende einen zusätzlichen Booleschen Test hinzufügen, den Sie wirklich aus keinem Grund brauchen.
Shadur

@ Shadur Mir sind zusätzliche Boolesche Tests und ungenutzte myStringErgebnisse bekannt. Ich kopiere gerade die Funktion von OP. Ich werde es in etwas ändern, das realistischer aussieht. Boolescher Test ist trivial und sollte kein Problem sein.
Tia

1
@Shadur Wir machen hier keine Optimierung, oder? Mein Ziel ist es, meinen Code einfach zu lesen und zu warten, und ich persönlich würde das mit den Kosten eines Referenz-Null-Schecks bezahlen.
Bis zum

1
@tia Ich mag den Geist Ihres Codes. Anstatt den Benutzernamen zweimal auszuwerten, würde ich die erste Auswertung in einer Variablen erfassen und diese zurückgeben.
Chuck Conway

-1

Meine Faustregel ist, entweder eine If-Return-Operation ohne Sonst (meistens für Fehler) oder eine vollständige If-else-If-Kette über alle Möglichkeiten durchzuführen.

Auf diese Weise vermeide ich es, zu ausgefeilt mit meiner Verzweigung umzugehen, da ich immer dann, wenn ich das tue, die Anwendungslogik in meine Ifs codiere, was die Pflege erschwert (z. B. wenn eine Aufzählung OK oder ERR ist und ich alle meine Ifs schreibe) Ausnutzen der Tatsache, dass! OK <-> ERR es nervt, dem Enum beim nächsten Mal eine dritte Option hinzuzufügen.)

In Ihrem Fall würde ich beispielsweise ein einfaches if verwenden, da "return if null" mit Sicherheit ein allgemeines Muster ist und Sie sich wahrscheinlich nicht um eine dritte Möglichkeit kümmern müssen (zusätzlich zu null / not null). in der Zukunft.

Wäre der Test jedoch etwas, das eine eingehendere Prüfung der Daten durchführt, würde ich mich stattdessen zu einem vollständigen Wenn-Andern irren.


-1

Ich denke, es gibt eine Menge Fallstricke bei Beispiel 2, die später zu unbeabsichtigtem Code führen könnten. Zunächst einmal basiert der Fokus hier auf der Logik, die die Variable 'myString' umgibt. Aus diesem Grund sollten alle Tests von Bedingungen explizit in einem Codeblock durchgeführt werden, der bekannte und Standard- / unbekannte Logik berücksichtigt .

Was passiert, wenn später ungewollt Code in Beispiel 2 eingefügt wird, der die Ausgabe erheblich verändert hat?

   if (myString == null)
   {
      return false;
   }

   //add some v1 update code here...
   myString = "And the winner is: ";
   //add some v2 update code here...
   //buried in v2 updates the following line was added
   myString = null;
   //add some v3 update code here...
   //Well technically this should not be hit because myString = null
   //but we already passed that logic
   myString = "Name " + myString;
   // Do something more here...
   return true;

Ich denke, mit dem elseBlock, der unmittelbar auf die Überprüfung auf eine Null folgt, hätten Programmierer, die die Erweiterungen für die zukünftigen Versionen hinzugefügt haben, die gesamte Logik zusammenaddiert, da wir jetzt eine Folge von Logik haben, die für die ursprüngliche Regel nicht beabsichtigt war (Rückgabe, wenn der Wert ist) Null).

Ich bin fest davon überzeugt, dass einige der C # -Richtlinien zu Codeplex (Link dazu hier: http://csharpguidelines.codeplex.com/ ) Folgendes enthalten:

Msgstr "" "Fügen Sie einen beschreibenden Kommentar hinzu, wenn der Standardblock (else) leer sein soll. Wenn dieser Block nicht erreicht werden soll, lösen Sie eine InvalidOperationException aus, um zukünftige Änderungen zu erkennen, die durch die vorhandenen Fälle fallen könnten. Dies sorgt für besseren Code, weil Über alle Pfade, die der Code zurücklegen kann, wurde nachgedacht. "

Ich halte es für eine gute Programmierpraxis, bei Verwendung von Logikblöcken wie diesem immer einen Standardblock hinzuzufügen (if-else, case: default), um alle Codepfade explizit zu berücksichtigen und den Code keinen unbeabsichtigten logischen Konsequenzen auszusetzen.


Wie kann das Fehlen eines anderen in Beispiel zwei nicht alle Fälle berücksichtigen? Das beabsichtigte Ergebnis wird weiterhin ausgeführt. Das Hinzufügen einer else-Klausel erhöht in diesem Fall nur die Komplexität der Methode.
Chuck Conway

Ich bin nicht einverstanden mit der Fähigkeit, nicht alle betroffenen Logiken in einem prägnanten und deterministischen Block in Frage zu stellen. Der Fokus liegt auf der Manipulation des myStringWertes und der Code, der dem ifBlock folgt, ist die resultierende Logik, wenn der String! = Null ist. Da der Fokus auf der zusätzlichen Logik liegt, die diesen bestimmten Wert manipuliert, sollte er meines Erachtens in einem elseBlock gekapselt werden . Ansonsten eröffnet sich das Potenzial, wie ich gezeigt habe, die Logik ungewollt zu trennen und ungewollte Konsequenzen einzuführen. Könnte nicht die ganze Zeit passieren, aber das war wirklich eine Best-Practice- Frage.
Atconway
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.