it-swarm.com.de

Wann sollte es unter bestimmten Bedingungen verwendet werden?

Wann sollte es unter bestimmten Bedingungen verwendet werden?

1)

ein)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } else {
        return 1;
    }
}

oder

b)

long int multiplyNumbers(int n)
{
    if (n >= 1) {
        return n*multiplyNumbers(n-1);
    } 

    return 1;
}

2)

ein)

int max(int num1, int num2) {
   int result;

   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }

   return result; 
}

oder

b)

int max(int num1, int num2) {

   if (num1 > num2) {
      return num1;
   } else {
      return num2;
   }
}

oder

c)

int max(int num1, int num2) {
   if (num1 > num2) {
      return num1;
   } 

   return num2;
}

Gibt es eine Regel, wann Sie etwas anderes verwenden sollen?

Nehmen die if with else-Anweisungen mehr Speicherplatz in Anspruch? Einerseits sind sie besser lesbar, andererseits ist zu viel Verschachtelung schlecht lesbar.

Wenn ich Ausnahmen auslöse, ist es besser, keine anderen zu verwenden, aber wenn dies gewöhnliche bedingte Operationen sind, wie in meinen Beispielen?

15
zohub

Ich kann Ihnen sagen, welche I verwenden würde und aus welchen Gründen, aber ich denke, dass es hier so viele Meinungen wie Community-Mitglieder geben wird ...

1) Weder noch. Ich würde schreiben:

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    }
    return n*multiplyNumbers(n-1);
}

Dies liegt daran, dass mir die Idee des "vorzeitigen Ausstiegs" in den Sonderfällen gefällt, und dies erinnert mich an dieses Szenario. Die allgemeine Regel lautet: Wenn Sie einen Sonderfall oder einen allgemeinen Fall behandeln müssen, behandeln Sie zuerst den Sonderfall. Vermutlich wird der Sonderfall (z. B. Fehler oder eine Randbedingung) schnell behandelt, und der Rest des Codes ist immer noch nicht zu weit von der If-Bedingung entfernt, sodass Sie nicht einige Codezeilen überspringen können, um ihn zu untersuchen. Dies erleichtert auch das Lesen der allgemeinen Fallbearbeitung, da Sie nur den Funktionskörper nach unten scrollen müssen.

Mit anderen Worten, anstatt:

void foo(int bar)
{
    if (!special_case_1) {
        if (!special_case_2) {
            return handle_general_case();
        }
        else {
            return handle_special_case_2();
        }
    }
    else {
        return handle_special_case_1();
    }
}

Ich befürworte das Schreiben:

int foo(int bar)
{
    if (special_case_1) {
        return handle_special_case_1();
    }
    if (special_case_2) {
        return handle_special_case_2();
    }
    return handle_general_case();
}

Sie werden feststellen, dass ich, wenn es machbar ist, es vorziehen würde, hier keine zusätzliche Variable 'result' einzuführen, hauptsächlich, weil es mir dann egal ist, ob sie initialisiert wird oder nicht. (Wenn Sie es einführen, initialisieren Sie es entweder oben, um es in jedem Zweig zu überschreiben, oder Sie tun es nicht, und Sie vergessen möglicherweise, es in einem Zweig zu initialisieren. Außerdem, selbst wenn Sie es in jedem Zweig initialisieren Zweigstelle, Ihr statischer Code-Analysator sieht dies möglicherweise nicht und beschwert sich möglicherweise ohne Grund.)

2) Ich würde (b) verwenden (mit weggeworfener Erklärung des 'Ergebnisses'). Dies liegt daran, dass ich in diesem Fall nicht sagen kann, dass entweder num1 <num2 oder num1> = num2 ein Sonderfall ist. Sie sind meinen Augen gleich und daher wird (b) gegenüber (c) bevorzugt. (Ich habe bereits erklärt, warum ich in diesem Fall keine zusätzliche Variable einführen würde, sodass (a) in meinen Augen (b) unterlegen ist.)

42
user285148

Es gibt keine feste Regel, aber hier einige grundlegende Richtlinien, die mein Team befolgen soll:

  • Vermeiden Sie tief verschachtelte Wenns
  • Ordnen Sie Codeblöcke so an, dass sie sich dem Zustand nähern, der sie betrifft
  • Bei den meisten Validierungsprüfungen, Nullprüfungen oder typischen Programmierlogiken können Sie else -Anweisungen mithilfe des Schutzmusters vollständig vermeiden.
  • Für die Geschäftslogik - bei der Sie nicht nur nach Edge-Bedingungen suchen, sondern auch den Pfad auswählen - ist es wichtiger, die Logik im Code auf eine Weise darzustellen, die der Spezifikation leicht zugeordnet werden kann. Wenn die Spezifikation also else-Anweisungen (oder Pfeile in einem Flussdiagramm) enthält, ist es in Ordnung, diese im Code zu haben.

Einfaches Beispiel. Anstatt

if (condition1)
{
    if (condition2)
    {
        DoSomething();
        return result;
    }
    else
    {
        return errorCode;
    }
}
else
{
    return errorCode;
}

Es ist leichter zu lesen:

if (!condition1) return errorCode;
if (!condition2) return errorCode;
DoSomething();
return result;
  1. Wir haben das Verschachtelungsniveau reduziert. Menschen, die den Code lesen, müssen keine Bedingungen in ihrem Kopf kombinieren, um die Logik herauszufinden.

  2. Die jedem bedingten Ausdruck zugeordnete Aktion befindet sich direkt neben der if-Anweisung, die bestimmt, ob sie ausgeführt wird. Personen, die den Code lesen, müssen die geschweiften Klammern nicht nachverfolgen, um herauszufinden, welcher Code von welcher Bedingung betroffen ist, und müssen nicht nach oben und unten scrollen, um den Code zu verstehen.

  3. Dieser Ansatz folgt dem Schutzmuster , das zyklomatische Komplexität reduziert, von dem gezeigt wurde, dass es die Fehlerraten reduziert.

Siehe auch:

So vermeiden Sie, wenn Ketten

Codemuster mit der geringsten Komplixität

Abflachender Pfeilcode

15
John Wu

Es gibt keine universelle Regel, und Sie werden vernünftige Leute finden, die sich nicht darüber einig sind, ob es jemals eine gute Idee ist, mehrere Renditen zu erzielen.

In Fällen, in denen Sie den Code mit einer einzigen Rückgabe genauso einfach gestalten können, ist dies vorzuziehen. Z.B.:

int max(int num1, int num2) {
   return (num1 > num2) ? num1 : num2;
}

In Fällen, in denen Sie den Code durch die Verwendung mehrerer Rückgaben vereinfachen können, halte ich es für akzeptabel, wenn er der Redewendung "Early Exit" folgt:

long int multiplyNumbers(int n)
{
    if (n < 1) {
        return 1;
    } 
    return n*multiplyNumbers(n-1);
}

Das heißt, Sie haben einige Sonderfälle, die oben in der Funktion behandelt werden können, wobei die komplexere Logik übersprungen wird. Abgesehen davon würde ich jedoch davon abraten, mehrere Rückgaben oder Rückgaben in verschachtelten Blöcken zu haben, da dies die Verfolgung der Logik in komplexeren Funktionen erschwert.

10
JacquesB

In Bezug auf die Speichernutzung:

  • Darüber sollten Sie sich nicht einmal Sorgen machen.
  • Nein, im Allgemeinen sollte es keinen wirklichen Unterschied geben.
  • Der Compiler wird die Dinge nach eigenem Ermessen ändern - die Lesbarkeit ist hier wirklich das einzige Problem.

Ich glaube nicht, dass es eine absolut endgültige Antwort gibt. Es gibt jedoch einige Aspekte, die Sie berücksichtigen sollten.

Beispiel 1

Ich würde es vorziehen, früh zurückzukehren, wenn die Bedingung einen Sonderfall darstellt.

unsigned factorial(unsigned n)
{
    if (n <= 1) {
        return 1;  // this is a special case
    }

    return n * factorial(n-1);  // this is the "normal" case
}

Wenn Sie zwei Fälle haben und nicht entscheiden können, welcher Fall etwas Besonderes ist, wird ein if/else Ansatz könnte am sinnvollsten sein.

Beispiel 2

Es wird Ausnahmen geben, aber ich würde es vermeiden, eine Variable zu deklarieren, die erst später verwendet wird. Also würde ich 2a) gegenüber 2b) wählen. 2c) macht Sinn, wenn num1 > num2 ist etwas Besonderes, wie oben beschrieben.

4
doubleYou

Es gibt keine feste Regel. Mit der Erfahrung werden Sie verschiedene Regeln herausfinden, die Sie in verschiedenen Fällen anwenden werden. In Ihrem Beispiel haben Sie zwei Zweige, die symmetrisch und einfach sind. Verwenden Sie also nur if (...) {return x; } else {return y; }}

Dinge zu beachten: Manchmal haben Sie eine größere Funktion mit einigen einfachen Sonderfällen, die Sie zuerst loswerden müssen. if (einfache Bedingung) return x; if (eine weitere einfache Bedingung) gibt y zurück; gefolgt von viel Code.

Eine weitere zu berücksichtigende Sache: Zum Debuggen möchten Sie möglicherweise einen Haltepunkt festlegen, wenn die Funktion zurückgegeben wird, sodass Sie nur eine return-Anweisung möchten. (Wenn Sie einen Debugger haben, mit dem Sie bei der Rückkehr der Funktion auch bei mehreren return-Anweisungen einen Fehler machen können, haben Sie Glück).

Und Sie möchten keine tief verschachtelten if/else-Sequenzen, mit dem else der ersten, wenn etwa eine halbe Meile entfernt, also ordnen Sie Ihren Code entsprechend an.

1
gnasher729

Wie andere bereits erwähnt haben: Es ist normalerweise vorzuziehen, den glücklichen Weg so gering wie möglich einzurücken und bei Bedarf frühzeitig zurückzukehren.

Nebenbei bemerkt würde ich in diesen beiden einfachen Fällen die Verwendung eines bedingten Ausdrucks empfehlen, da dies so ziemlich der ideale Anwendungsfall dafür ist:

1) return (n < 1) ? 1 : n*multiplyNumbers(n-1);

2) return (num2 < num1) ? num1 : num2;

Ich würde Methode 2.a mit Sicherheit vermeiden. Es ist lang und die temporäre Variable ist eine zusätzliche Unordnung ohne Lesbarkeitsvorteil (z. B. hat sie keinen informativen Namen).