Sollte „else“ in Situationen verwendet werden, in denen der Kontrollfluss ihn überflüssig macht?

18

Manchmal stoße ich auf Code, der dem folgenden Beispiel ähnelt (was diese Funktion genau tut, ist nicht Gegenstand dieser Frage):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Wie Sie sehen, ifwerden else ifund else-Anweisungen in Verbindung mit der returnAnweisung verwendet. Für einen zufälligen Betrachter scheint dies ziemlich intuitiv zu sein, aber ich denke, es wäre eleganter (aus der Sicht eines Softwareentwicklers), das else-s wegzulassen und den Code wie folgt zu vereinfachen:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

Dies ist sinnvoll, da alles, was auf eine returnAnweisung folgt (im selben Bereich), niemals ausgeführt wird, sodass der obige Code semantisch dem ersten Beispiel entspricht.

Welche der oben genannten Optionen passt besser zu den bewährten Codierungsmethoden? Gibt es bei beiden Methoden Nachteile hinsichtlich der Lesbarkeit des Codes?

Bearbeiten: Es wurde ein doppelter Vorschlag mit dieser Frage als Referenz gemacht. Ich glaube, meine Frage berührt ein anderes Thema, da ich nicht nach doppelten Aussagen frage, wie sie in der anderen Frage dargestellt sind. Beide Fragen zielen darauf ab, Wiederholungen zu reduzieren, wenn auch auf leicht unterschiedliche Weise.

Nashorn
quelle
14
Das elseist ein geringeres Problem, das größere Problem ist, dass Sie offensichtlich zwei Datentypen aus einer einzelnen Funktion zurückgeben, was die API der Funktion unvorhersehbar macht.
Andy
3
E_CLEARLY_NOTADUPE
Kojiro
3
@ DavidPacker: Mindestens zwei; könnte drei sein. -1ist eine Zahl, falseist ein Boolescher Wert und valuewird hier nicht angegeben, sodass es sich um einen beliebigen Objekttyp handeln kann.
Yay295
1
@ Yay295 Ich hatte gehofft, das valueist eigentlich eine ganze Zahl. Wenn es irgendetwas sein kann, ist das noch schlimmer.
Andy
@DavidPacker Dies ist ein sehr wichtiger Punkt, insbesondere im Hinblick auf eine Frage zu guten Praktiken. Ich stimme Ihnen und Yay295 zu, jedoch dienen die Codebeispiele als Beispiel für eine nicht verwandte Codierungstechnik. Dennoch denke ich daran, dass dies eine wichtige Angelegenheit ist und im tatsächlichen Code nicht übersehen werden sollte.
Nashorn

Antworten:

23

Ich mag das ohne elseund hier ist warum:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Weil das nichts kaputt machte, was es nicht sollte.

Hassen Sie die gegenseitige Abhängigkeit in all ihren Formen (einschließlich der Benennung einer Funktion check2()). Isolieren Sie alles, was isoliert werden kann. Manchmal braucht man das, elseaber das sehe ich hier nicht.

kandierte_orange
quelle
Ich bevorzuge dies im Allgemeinen auch aus dem hier angegebenen Grund. Normalerweise setze ich jedoch einen Kommentar am Ende jedes ifBlocks von } //else, um beim Lesen des Codes zu verdeutlichen, dass die einzige beabsichtigte Ausführung nach dem ifCodeblock darin besteht, dass die Bedingung in der ifAnweisung falsch war. Ohne etwas wie dieses, insbesondere wenn der Code innerhalb des ifBlocks komplexer wird, kann es sein, dass nicht klar ist, wer den Code verwaltet, dass die Absicht bestand, niemals nach dem ifBlock auszuführen , wenn die ifBedingung wahr ist.
Makyen
@Makyen du sprichst einen guten Punkt an. Eine, die ich zu ignorieren versuchte. Ich glaube auch, dass nach dem etwas getan werden sollte if, um zu verdeutlichen, dass dieser Codeabschnitt fertig ist und die Struktur fertig ist. Dazu tue ich, was ich hier nicht getan habe (weil ich nicht durch andere Änderungen vom Hauptpunkt des OP ablenken wollte). Ich tue das Einfachste, was ich kann, um all das zu erreichen, ohne abzulenken. Ich füge eine Leerzeile hinzu.
candied_orange
27

Ich denke, es hängt von der Semantik des Codes ab. Wenn Ihre drei Fälle voneinander abhängig sind, geben Sie dies explizit an. Dies erhöht die Lesbarkeit des Codes und erleichtert das Verständnis für andere. Beispiel:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

Hier sind Sie eindeutig auf den Wert von xin allen drei Fällen angewiesen. Wenn Sie den letzten weglassen elsewürden, wäre dies weniger klar.

Wenn Ihre Fälle nicht direkt voneinander abhängen, lassen Sie es weg:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

Es ist schwer, dies in einem einfachen Beispiel ohne Kontext zu zeigen, aber ich hoffe, Sie verstehen meinen Standpunkt.

André Stannek
quelle
6

Ich ziehe die zweite Option (trennen ifsohne else ifund früh return) ABER das ist , solange die Codeblöcke kurz sind .

Wenn Codeblöcke lang sind, ist es besser else if, sie zu verwenden , da Sie ansonsten nicht genau wissen, über welche Ausstiegspunkte der Code verfügt.

Beispielsweise:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

In diesem Fall ist es besser else if, den Rückgabewert in einer Variablen zu speichern und am Ende nur einen Rückgabesatz zu haben.

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

Da man sich jedoch darum bemühen sollte, dass Funktionen kurz sind, würde ich sagen, halten Sie sie kurz und beenden Sie sie früh wie in Ihrem ersten Code-Snippet.

Tulains Córdova
quelle
1
Ich bin mit Ihrer Prämisse einverstanden, aber solange wir darüber diskutieren, wie Code sein sollte, können wir uns vielleicht auch darauf einigen, dass Codeblöcke sowieso kurz sein sollten. Ich denke, es ist schlimmer, einen langen Codeblock zu haben, der nicht in Funktionen zerlegt ist, als etwas anderes zu verwenden, wenn ich wählen müsste.
Kojiro
1
Ein merkwürdiges Argument. returnliegt innerhalb von 3 Zeilen, ist ifalso nicht anders als else ifnach 200 Codezeilen. Der hier vorgestellte Single Return-Stil ist eine Tradition von c und anderen Sprachen, die keine Fehler mit Ausnahmen melden. Es ging darum, einen einzigen Ort zum Bereinigen von Ressourcen zu schaffen. Ausnahmesprachen verwenden dafür einen finallyBlock. Ich nehme an, dass dies auch einen Platz schafft, an dem ein Haltepunkt gesetzt werden kann, wenn Ihr Debugger nicht zulässt, dass Sie einen Haltepunkt auf die Funktionen setzen, die die geschweifte Klammer schließen.
candied_orange
4
Ich mag die Aufgabe retValüberhaupt nicht. Wenn Sie eine Funktion sicher verlassen können, tun Sie dies sofort, ohne den sicheren Wert zuzuweisen, der an eine Variable eines einzelnen Exits der Funktion zurückgegeben werden soll. Wenn Sie den einzelnen Rückgabewert in einer sehr langen Funktion verwenden, vertraue ich im Allgemeinen anderen Programmierern nicht und suche im Rest der Funktion auch nach anderen Änderungen des Rückgabewerts. Schnell scheitern und früh zurückkehren sind unter anderem zwei Regeln, nach denen ich lebe.
Andy
2
Meiner Meinung nach ist es für lange Blöcke noch schlimmer. Ich versuche, Funktionen so früh wie möglich zu beenden, unabhängig vom äußeren Kontext.
Andy
2
Ich bin mit DavidPacker hier. Der einzelne Rückgabestil zwingt uns, alle Zuordnungspunkte sowie den Code nach den Zuordnungspunkten zu studieren. Das Studium der Austrittspunkte eines frühen Rückkehrstils wäre mir lang oder kurz vorzuziehen. Solange die Sprache einen finalen Block zulässt.
candied_orange
4

Ich benutze beides unter verschiedenen Umständen. Bei einer Validierungsprüfung lasse ich das übrige weg. In einem Kontrollfluss verwende ich das else.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

vs

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

Der erste Fall sieht eher so aus, als würden Sie zuerst alle Voraussetzungen prüfen, diese aus dem Weg räumen und dann zum eigentlichen Code übergehen, den Sie ausführen möchten. Daher gehört der Code, den Sie unbedingt ausführen möchten, direkt zum Funktionsumfang. darum geht es in der Funktion wirklich.
Der zweite Fall sieht eher so aus, als ob beide Pfade gültiger auszuführender Code sind, der für die Funktion genauso relevant ist wie der andere, basierend auf einer bestimmten Bedingung. Als solche gehören sie in ähnlichen Umfangsebenen zueinander.

Altainia
quelle
0

Die einzige Situation, in der ich jemals gesehen habe, dass es wirklich darauf ankommt, ist folgender Code:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

Hier stimmt eindeutig etwas nicht. Das Problem ist, es ist nicht klar, ob returnes hinzugefügt oder Arrays.asListentfernt werden sollte. Sie können dies nicht beheben, ohne die zugehörigen Methoden eingehender zu untersuchen. Sie können diese Mehrdeutigkeit vermeiden, indem Sie immer vollständige if-else-Blöcke verwenden. Dies:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

Kompiliert nicht (in statisch überprüften Sprachen), es sei denn, Sie fügen explizite Rückgabe zuerst hinzu if.

Einige Sprachen erlauben nicht einmal den ersten Stil. Ich versuche es nur im zwingenden Kontext oder als Voraussetzung für lange Methoden zu verwenden:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}
Banthar
quelle
-2

Es ist wirklich verwirrend, drei Abbrüche von dieser Funktion zu haben, wenn Sie versuchen, dem Code zu folgen und schlechte Praktiken anzuwenden.

Wenn Sie einen einzelnen Austrittspunkt für die Funktion haben, sind die anderen erforderlich.

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

Lassen Sie uns sogar noch weiter gehen.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

Fairerweise könnte man für eine einzelne vorzeitige Rückkehr bei der Eingabevalidierung argumentieren. Vermeiden Sie es einfach, die gesamte Masse in ein Wenn zu packen.

Ewan
quelle
3
Dieser Stil stammt aus Sprachen mit expliziter Ressourcenverwaltung. Ein einziger Punkt wurde benötigt, um zugewiesene Ressourcen freizugeben. In Sprachen mit automatischer Ressourcenverwaltung ist ein einzelner Rückgabepunkt nicht so wichtig. Sie sollten sich eher darauf konzentrieren, die Anzahl und den Umfang der Variablen zu reduzieren.
Banthar
a: In der Frage ist keine Sprache angegeben. b: Ich denke, Sie irren sich in diesem Punkt. Es gibt viele gute Gründe für eine einmalige Rücksendung. es reduziert die Komplexität und verbessert die Lesbarkeit
Ewan
1
Manchmal reduziert es die Komplexität, manchmal erhöht es sie. Siehe wiki.c2.com/?ArrowAntiPattern
Robert Johnson
Nein, ich denke, es reduziert immer die zyklomatische Komplexität, siehe mein zweites Beispiel. check2 läuft immer
Ewan
Die vorzeitige Rückkehr ist eine Optimierung, die den Code durch Verschieben von Code in eine Bedingung erschwert
Ewan
-3

Ich ziehe es vor, die redundante else-Anweisung wegzulassen. Wann immer ich es sehe (bei der Codeüberprüfung oder beim Refactoring), frage ich mich, ob der Autor den Kontrollfluss verstanden hat und ob möglicherweise ein Fehler im Code vorliegt.

Wenn der Autor der Meinung ist, dass die else-Anweisung notwendig ist und daher die Auswirkungen der return-Anweisung auf den Kontrollfluss nicht versteht, ist ein solches Unverständnis sicherlich eine Hauptursache für Fehler in dieser Funktion und im Allgemeinen.

Andreas Warberg
quelle
3
Obwohl ich Ihnen zustimme, ist dies keine Meinungsumfrage. Bitte sichern Sie Ihre Behauptungen, sonst werden Sie von den Wählern nicht freundlich behandelt.
candied_orange