it-swarm.com.de

Klarstellung des Hinweises "Vermeiden Sie das Wenn"

Die Experten für sauberen Code raten davon ab, if/else Zu verwenden, da dadurch ein unlesbarer Code erstellt wird. Sie schlagen vor, lieber IF zu verwenden und nicht ohne wirkliche Notwendigkeit bis zum Ende einer Methode zu warten.

Dieser if/else Rat verwirrt mich. Sagen sie, ich sollte nicht if/elseüberhaupt (!) verwenden oder if/else Verschachteln vermeiden?

Wenn sie sich auf die Verschachtelung von if/else Beziehen, sollte ich dann nicht einmal eine einzige Verschachtelung durchführen oder sie auf maximal 2 Verschachtelungen beschränken (wie einige empfehlen)?

Wenn ich Single Nesting sage, meine ich das:

    if (...) {

        if (...) {

        } else {

        }

    } else {

    }

EDIT

Auch Tools wie Resharper schlagen vor, if/else - Anweisungen neu zu formatieren. Normalerweise werden sie mit einer eigenständigen Anweisung if und manchmal sogar mit einem ternären Ausdruck konzertiert.

39
deviDave

Ich denke, dieser Rat kam von einer Software-Metrik, die Zyklomatische Komplexität oder Bedingte Komplexität check diese Wiki-Seite nannte

Definition:

Die zyklomatische Komplexität eines Abschnitts des Quellcodes ist die Anzahl der linear unabhängigen Pfade durch den Quellcode. Wenn der Quellcode beispielsweise keine Entscheidungspunkte wie IF-Anweisungen oder FOR-Schleifen enthält, wäre die Komplexität 1, da es nur einen einzigen Pfad durch den Code gibt. Wenn der Code eine einzelne IF-Anweisung hätte, die eine einzelne Bedingung enthält, würde es zwei Pfade durch den Code geben, einen Pfad, in dem die IF-Anweisung als TRUE ausgewertet wird, und einen Pfad, in dem die IF-Anweisung als FALSE ausgewertet wird.

Damit

Sagen sie, dass ich if/else überhaupt nicht verwenden sollte (!) Oder um zu vermeiden, wenn/else verschachtelt wird?

Nein, du solltest es nicht vermeiden, wenn/sonst überhaupt! Sie sollten jedoch auf die zyklomatische Komplexität Ihres Codes achten. Eine höhere Codekomplexität weist tendenziell mehr Fehler auf. Nach this wäre die richtige Grenze 11 . Ein Tool namens Code Complete kategorisiert die Punktzahl wie folgt Ref. :

  • 0-5 - die Routine ist wahrscheinlich in Ordnung
  • 6-10 - Überlegen Sie, wie Sie die Routine vereinfachen können
  • 10 + - Teilen Sie einen Teil der Routine in eine zweite Routine auf und rufen Sie sie von der ersten Routine auf

Wie berechnet man die zyklomatische Komplexität "im Grunde":

Aus Beispiel hier : Betrachten Sie den folgenden Pseudocode:

if (A=354) {

    if (B>C) {/*decision 1*/ A=B} else {/*decision 2*/ A=C}

   }/*decision 3 , in case A is NOT equal 354*/ 
print A

bitte beachten Sie, dass diese Metrik die Anzahl der Entscheidungen in Ihrem Programm/Code betrifft. Aus dem obigen Code können wir also sagen, dass wir 3 Entscheidungen haben (3 Werte von A).

berechnen wir es formal. Betrachten Sie also den folgenden Kontrollfluss des Codes: enter image description here

Die Komplexität M ist dann definiert als: Ref

M = E − N + 2P

wo

E = the number of edges of the graph
N = the number of nodes of the graph
P = the number of connected components (exit nodes).

durch Anwenden der Formel E(flow lines) = 8, N (nodes)= 7, P (exit nodes) =1 dann M = 8-7 + (2*1) = 3

* Es gibt auch eine andere Definition, die jedoch nahe beieinander liegt: M = E − N + P Überprüfen Sie die Theorie auf Referenz .

Bitte beachten Sie, dass viele von statische Code-Analyse-Tools die zyklomatische Komplexität Ihres Codes berechnen können.

43
Abdurahman

IMO konzentrieren sich die vorherigen Antworten auf Edge-Fälle und Code, die ohnehin neu geschrieben werden sollten. Hier sind einige Fälle, die ich fast überall sehe, in denen if/else Durch if oder ein anderes Sprachkonstrukt hätte ersetzt werden sollen.

Hinweis: Ich mache das Antwort-Community-Wiki. Sie können es jederzeit ändern und weitere Beispiele für Fälle hinzufügen, in denen if/else Normalerweise zu fehlerhaftem Code führt.

Anfänger, seid gewarnt ! if/else Kann einen unlesbaren Code erstellen, wenn er nicht sorgfältig verwendet wird. Die nächsten Beispiele zeigen die häufigsten Fälle, in denen if/else Leicht missbraucht wird.

1. Schutzklausel

Ich habe aufgehört zu zählen, wie oft ich so etwas gesehen habe:

void Demo(...)
{
    if (ok_to_continue)
    {
        ... // 20 lines here.
    }
    else
    {
        throw new SomeException();
    }
}

Niemand braucht zusätzliche Einrückungen. Der gleiche Code hätte folgendermaßen geschrieben werden können:

void Demo(...)
{
    if (!ok_to_continue)
    {
        throw new SomeException();
    }

    ... // No indentation for the next 20 lines.
}

Regel: Wenn Sie eine else - Anweisung entfernen können, indem Sie die if - Prüfung invertieren, einen Fehler behandeln und sofort beenden (andernfalls fahren Sie fort), dann tun Sie dies.

2. Codeduplizierung

Dieser ist auch ziemlich häufig.

if (!this.useAlternatePanel)
{
    currentInput = this.defaultTextBox.Text;
    bool missingInput = currentInput.Length == 0;
    this.defaultProcessButton.Enabled = !missingInput;
    this.defaultMissingInputHelpLabel.Visible = missingInput;
}
else
{
    currentInput = this.alternateTextBox.Text;
    bool missingInput = currentInput.Length == 0;
    this.alternateProcessButton.Enabled = !missingInput;
    this.alternateMissingInputHelpLabel.Visible = missingInput;
}

Dies ist ein hervorragendes Beispiel für Anfängercode, da Anfänger nicht immer sehen würden, wie ein solcher Code überarbeitet werden kann. Eine der Möglichkeiten besteht darin, eine Karte mit Steuerelementen zu erstellen:

var defaultControls = new CountriesProcessingControls(
    this.defaultTextBox,
    this.defaultProcessButton,
    this.defaultMissingInputHelpLabel);

var alternateControls = new CountriesProcessingControls(
    this.alternateTextBox,
    this.alternateProcessButton,
    this.alternateMissingInputHelpLabel);

void RefreshCountriesControls(CountriesProcessingControls controls)
{
    currentInput = controls.textBox.Text;
    bool missingInput = currentInput.Length == 0;
    controls.processButton.Enabled = !missingInput;
    controls.missingInputHelpLabel.Visible = missingInput;
}

RefreshCountriesControls(this.useAlternatePanel ? alternateControls : defaultControls);

Regel: Wenn der Block in else fast identisch mit dem Block in if ist, machen Sie es falsch und verursachen eine Codeduplizierung.

3. Ich versuche zwei Dinge zu tun

Das Gegenteil ist auch üblich:

if (...)
{
    foreach (var dateInput = this.selectedDates)
    {
        var isConvertible = convertToDate(dateInput);
        if (!isConvertible)
        {
            this.invalidDates.add(dateInput);
        }
    }
}
else
{
    var languageService = this.initializeLanguageService();
    this.Translated = languageService.translate(this.LastInput);
    if (this.Translated != null)
    {
        this.NotificationScheduler.append(LAST_INPUT_TRANSLATED);
    }
}

Der Block in if hat nichts mit dem Block in else zu tun. Das Kombinieren von zwei Blöcken in derselben Methode macht die Logik schwer verständlich und fehleranfällig. Es ist auch äußerst schwierig, einen Namen für die Methode zu finden und sie richtig zu kommentieren.

Regel: Vermeiden Sie es, Blöcke, die nichts gemeinsam haben, in einem if/else - Block zu kombinieren.

4. Objektorientierte Programmierung

Diese Art der Codierung ist auch bei Anfängern häufig:

// `animal` is either a dog or a cat.
if (animal.IsDog)
{
    animal.EatPedigree();
    animal.Bark();
}
else // The animal is a cat.
{
    animal.EatWhiskas();
    animal.Meow();
}

In der objektorientierten Programmierung sollte dieser Code nicht existieren. Es sollte stattdessen so geschrieben werden:

IAnimal animal = ...;
animal.Eat();
animal.MakeSound();

vorausgesetzt, dass jede Klasse (Cat : IAnimal und Dog : IAnimal) ihre eigene Implementierung dieser Methoden hat.

Regel: Wenn Sie objektorientiert programmieren, verallgemeinern Sie Funktionen (Methoden) und verwenden Sie Vererbung und Polymorphismus.

5. Zuweisen eines Wertes

Ich sehe diese überall in Codebasen, die von Anfängern geschrieben wurden:

int price;
if (this.isDiscount)
{
    price = -discount;
}
else
{
    price = unitCost * quantity;
}

Dies ist ein schrecklicher Code, da er nicht für Menschen geschrieben wurde.

  • Es ist irreführend. Es lässt Sie denken, dass die Hauptoperation hier die Wahl zwischen Rabatt- und Nichtrabattmodus ist, während die eigentliche Hauptoperation die Zuweisung von price ist.

  • Es ist fehleranfällig. Was ist, wenn später eine Bedingung geändert wird? Was ist, wenn eine von zwei Zuordnungen entfernt wird?

  • Es lädt noch schlechteren Code ein. Ich habe viele solche Teile gesehen, bei denen int price Stattdessen int price = 0; War, nur um die Compiler-Warnung zu beseitigen, wenn eine der Zuweisungen fehlt. Warnungen zu verbergen, anstatt sie zu lösen, ist eines der schlimmsten Dinge, die man tun kann.

  • Es gibt eine winzige Codeduplizierung, was bedeutet, dass mehr Zeichen ursprünglich eingegeben werden müssen und später weitere Änderungen vorgenommen werden müssen.

  • Es ist nutzlos lang.

Das gleiche könnte so geschrieben werden:

int price = this.isDiscount ? -discount : unitCost * quantity;

Regel: Verwenden Sie den ternären Operator anstelle von if/else - Blöcken für einfache Wertzuweisungen.

63

Mir gefällt wirklich, was Abdurahman in seiner Antwort gesagt hat.

Eine andere Möglichkeit, die Aussage „Vermeiden von Wenn/Sonst“ zu betrachten, besteht darin, in Entscheidungen zu denken.

Ziel ist es, Verwirrung beim Befolgen von Code zu vermeiden. Wenn Sie mehrere verschachtelte if/else-Anweisungen haben, wird es schwierig, das Hauptziel der Funktionen zu verstehen. Der Programmierer, der den Code schreibt, versteht jetzt die Logik . ABER, wenn derselbe Programmierer diesen Code ein oder zwei Jahre später noch einmal überprüft, wird das Verständnis im Allgemeinen vergessen. Ein neuer Programmierer, der sich den Code ansieht, muss erkennen können, was gerade passiert. Ziel ist es also, die Komplexität in einfache, leicht verständliche Funktionen aufzuteilen. Ziel ist es nicht, Entscheidungen zu vermeiden, sondern einen leicht verständlichen Logikfluss herzustellen.

8
Paul Grimes

Das Verschachteln von Codeblöcken, auch bekannt als "Pfeilspitzen" -Antimuster, verringert die Lesbarkeit des Codes, da Sie entweder weiter nach rechts verschieben müssen, um die verfügbare Breite für Zeilen mit "echtem" Code auf dem Bildschirm zu verringern, oder die Einrückung und andere verletzen müssen Formatierungsregeln, die im Normalfall das Lesen von Code erleichtern.

Ein typisches Beispiel:

if (X) {
    if (Y) {
       DoXandY();
    } else {
       DoXOnly();
    }
} else {
   DoDefault();
}

Dies ist ein einfaches Beispiel, und ich hätte keine Probleme mit einigen Ebenen dieser Art von Verschachtelung, vorausgesetzt, dass der gesamte LOC für diese Struktur nur etwa einen Bildschirm lang war und dass die Formatierung Die beteiligten Personen haben auf der linken Seite nicht zu viele Leerzeichen hinzugefügt (verwenden Sie möglicherweise 3 oder 4 Leerzeichen anstelle eines Tabulators, insbesondere wenn der Code in anderen Editoren angezeigt wird, in denen ein Tabulator 8 oder mehr Leerzeichen enthalten kann). Darüber hinaus können Sie den Ausgangszustand, unter dem die anderen verschachtelt sind, vergessen oder nicht leicht erkennen. Dies kann dazu führen, dass Sie sich in Ihrer Codebasis "verlaufen", insbesondere wenn Sie eine Struktur haben, in der X und Y unabhängig sind:

if (X) {
    if (Y) {
       DoXandY();
    } else {
       DoXOnly();
    }
} else {
   if (Y) {
       DoYOnly();
    } else {
       DoDefault();
    }
}

In diesem alternativen Universum zur spezifischen Frage des OP haben wir jetzt zwei "if (Y)" - Bedingungsprüfungen, und wenn wir in einem echten Code die verschachtelte übergeordnete Bedingung nicht sehen konnten, könnten wir denken, dass wir uns in einer solchen befinden Ein anderer Ort im Code als wir wirklich sind, und das verursacht Fehler.

Eine Möglichkeit, die Verschachtelung zu verringern, besteht darin, den äußeren Zustand auf den inneren Zustand zu verteilen. Beachten Sie Folgendes, bevor Sie zu Ihrem ursprünglichen Snippet zurückkehren (kein "DoYOnly ()"):

if (X && Y) {
    DoXandY();
} else if (X) {
    DoXOnly();
} else {
    DoDefault();
}

Schau Ma, keine Verschachtelung! Auf Kosten einer zusätzlichen Auswertung von X (die, wenn die Auswertung von X rechenintensiv ist, im Voraus ausgeführt und in einer booleschen Variablen gespeichert werden kann) machen Sie die Struktur zu einem einstufigen if/else-Entscheidungsbaum und wann Wenn Sie jeden Zweig bearbeiten, kennen Sie die Bedingung, die erfüllt sein muss, um ihn einzugeben. Sie müssen sich immer noch daran erinnern, was nicht wahr ist, um zu diesem "else if" zu gelangen. Da sich jedoch alles auf derselben Ebene befindet, sollte es einfacher sein, die vorherigen Zweige des Entscheidungsbaums zu identifizieren. Alternativ können Sie alle anderen Schlüsselwörter entfernen, indem Sie bei jedem Schritt eine vollständigere Version der Bedingung testen:

if (X && Y) {
    DoXandY();
} 
if (X && !Y) {
    DoXOnly();
} 
if(!X) {
    DoDefault();
}

Jetzt wissen Sie genau, was wahr sein muss, damit jede Anweisung ausgeführt wird. Dies führt jedoch zu einer Reihe anderer Lesbarkeitsprobleme. Es wird schwieriger zu erkennen, dass sich alle diese Anweisungen gegenseitig ausschließen (was Teil dessen ist, was ein "else" -Block für Sie tut).

Andere Möglichkeiten, das Verschachteln einzuschließen, umfassen das Umkehren einer oder mehrerer Bedingungen. Wenn sich der größte Teil oder der gesamte Code einer Funktion in einer if-Anweisung befindet, die eine Bedingung testet, können Sie sich diese Verschachtelungsebene normalerweise sparen, indem Sie stattdessen testen, ob die Bedingung nicht true ist, und ausführen Alles, was Sie in einem else-Block über die ursprüngliche if-Anweisung hinaus tun würden, bevor Sie die Funktion verlassen. Zum Beispiel:

if(!X) {
    DoDefault();
    return;
}

if(Y) {
    DoXAndY();
} else {
    DoXOnly();
}

Dies verhält sich genauso wie die beiden obigen Anweisungen, testet jedoch jede Bedingung nur einmal und hat keine verschachtelten ifs. Dieses Muster wird manchmal als "Schutzklausel" bezeichnet. Der Code unter der anfänglichen if-Anweisung wird niemals ausgeführt, wenn X falsch ist. Sie können also sagen, dass die if-Anweisung den Rest des Codes "schützt" (weshalb wir niemals testen müssen, ob X im Rest des Codes wahr ist. Wenn wir zu dieser Zeile kommen, ist X wahr, weil wir die Funktion sonst vorzeitig verlassen hätten. Der Nachteil ist, dass dies für Code, der ein kleinerer Teil einer größeren Funktion ist, nicht so gut funktioniert. Wenn es nach dem gesamten obigen Snippet eine "DoMoreWork ()" - Funktion gäbe, müsste diese sowohl im Guard-Klausel-Code als auch unter der verbleibenden bedingten Anweisung enthalten sein. Oder die Schutzklausel kann mithilfe des Musters "else if" in den verbleibenden Code integriert werden:

if(!X) {
    DoDefault();        
} else if(Y) {
    DoXAndY();
} else {
    DoXOnly();
}

DoMoreWork();

In diesem Beispiel wird DoMoreWork () immer ausgeführt, auch wenn X falsch ist. Das Hinzufügen des gleichen Aufrufs unter dem vorherigen Snippet mit der return-Anweisung würde dazu führen, dass DoMoreWork nur aufgerufen wird, wenn X wahr ist, unabhängig davon, ob Y wahr oder falsch ist.

7
KeithS

Zusätzlich zu den anderen Antworten kann der Rat auch bedeuten, dass if/else Gegebenenfalls durch if/else if Ersetzt werden sollte. Letzteres ist informativer und lesbarer (ausgenommen natürlich triviale Fälle wie if (x > 0) else if (x <= 0)).

if/else if Hat einen Vorteil mehr als nur besser lesbar zu sein (obwohl dies auch ein erheblicher Vorteil ist). Dies hilft dem Programmierer, Fehler zu vermeiden, falls je nach Zustand mehr als zwei Zweige vorhanden sind. Ein solcher Stil macht den Fall missed offensichtlicher.

1
superM

Der Grund, warum Sie empfohlen werden, nur if -Anweisungen und nicht else zu verwenden, besteht darin, den klassischen Fehler von if this then for everything else do this Zu vermeiden. Der Umfang von else ist schwer vorherzusagen.

Zum Beispiel; Nehmen wir an, ich programmiere die Steuerung für ein selbstfahrendes Auto.

if (light is red) then {
    stop car
} else {
    drive forward
}

Während die Logik gut ist. Wenn das Auto gelb leuchtet, fährt es weiter durch die Kreuzung. Verursacht wahrscheinlich einen Unfall, wenn er langsamer werden und anhalten sollte.

Ohne den if/else Ist die Logik weniger fehleranfällig.

if(light is red) then {
  stop car
}
if(light is yellow) then {
  slow down
}
if(light is green) then {
  drive forward
}

Während mein Code möglicherweise nicht das beste selbstfahrende Auto ist. Es zeigt nur den Unterschied zwischen einer mehrdeutigen Bedingung und expliziten Bedingungen. Darauf bezog sich der Autor wahrscheinlich auch. Verwenden Sie else nicht, um die Logik zu verkürzen.

1
Reactgular