it-swarm-eu.dev

Wie würden Sie verschachtelte IF-Anweisungen umgestalten?

Ich war in der Programmier-Blogosphäre unterwegs, als ich auf diesen Beitrag über GOTOs stieß:

http://giuliozambon.blogspot.com/2010/12/programmers-tabu.html

Hier spricht der Autor darüber, wie "man zu dem Schluss kommen muss, dass es Situationen gibt, in denen GOTOs für besser lesbaren und wartbaren Code sorgen", und zeigt dann ein ähnliches Beispiel:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Der Verfasser schlägt dann vor, dass die Verwendung von GOTOs das Lesen und Verwalten dieses Codes erheblich erleichtern würde.

Ich persönlich kann mir mindestens drei verschiedene Möglichkeiten vorstellen, um den Code zu reduzieren und lesbarer zu machen, ohne auf fließende GOTOs zurückgreifen zu müssen. Hier sind meine beiden Favoriten.

1 - Verschachtelte kleine Funktionen. Nehmen Sie jedes if und seinen Codeblock und verwandeln Sie es in eine Funktion. Wenn die boolesche Prüfung fehlschlägt, kehren Sie einfach zurück. Wenn es erfolgreich ist, rufen Sie die nächste Funktion in der Kette auf. (Junge, das klingt sehr nach Rekursion. Könnten Sie das in einer einzigen Schleife mit Funktionszeigern tun?)

2 - Sentinalvariable. Für mich ist das am einfachsten. Verwenden Sie einfach eine Variable blnContinueProcessing und prüfen Sie, ob sie in Ihrer if-Prüfung noch zutrifft. Wenn die Prüfung fehlschlägt, setzen Sie die Variable auf false.

Auf wie viele verschiedene Arten kann diese Art von Codierungsproblem überarbeitet werden, um die Verschachtelung zu verringern und die Wartbarkeit zu verbessern?

34
saunderl

Es ist wirklich schwer zu sagen, ohne zu wissen, wie die verschiedenen Prüfungen interagieren. Rigoroses Refactoring könnte angebracht sein. Das Erstellen einer Topologie von Objekten, die je nach Typ den richtigen Block ausführen, kann auch ein Strategiemuster oder ein Statusmuster bewirken.

Ohne zu wissen, was am besten zu tun ist, würde ich zwei mögliche einfache Refactorings in Betracht ziehen, die durch Extrahieren weiterer Methoden weiter refactorisiert werden könnten.

Die erste mag ich nicht wirklich, da ich sie immer als kleine Austrittspunkte in einer Methode mag (vorzugsweise eine).

if (!Check#1)
{ 
    return;
}
CodeBlock#1

if (!Check#2)
{
    return;
}
CodeBlock#2
...

Der zweite entfernt die mehrfachen Rückgaben, fügt aber auch viel Rauschen hinzu. (es entfernt grundsätzlich nur die Verschachtelung)

bool stillValid = Check#1
if (stillValid)
{
  CodeBlock#1
}

stillValid = stillValid && Check#2
if (stillValid)
{
  CodeBlock#2
}

stillValid = stillValid && Check#3
if (stillValid)
{
  CodeBlock#3
}
...

Letzteres kann gut in Funktionen umgestaltet werden, und wenn Sie ihnen gute Namen geben, ist das Ergebnis möglicherweise vernünftig. ';

bool stillValid = DoCheck1AndCodeBlock1()
stillValid = stillValid && DoCheck2AndCodeBlock2()
stillValid = stillValid && DoCheck3AndCodeBlock3()

public bool DoCheck1AndCodeBlock1()
{
   bool returnValid = Check#1
   if (returnValid)
   {
      CodeBlock#1
   }
   return returnValid
}

Alles in allem gibt es höchstwahrscheinlich viel bessere Möglichkeiten

33
KeesDijk

Dies wird aufgrund der Form des Codes mit korrekter Einrückung als "Pfeilcode" bezeichnet.

Jeff Atwood hatte einen guten Blog-Beitrag über Coding Horror darüber, wie man die Pfeile abflacht:

Abflachender Pfeilcode

Lesen Sie den Artikel für die vollständige Behandlung, aber hier sind die wichtigsten Punkte ..

  • Ersetzen Sie die Bedingungen durch Schutzklauseln
  • Zerlegen Sie bedingte Blöcke in separate Funktionen
  • Konvertieren Sie negative Schecks in positive Schecks
  • Kehren Sie immer opportunistisch so schnell wie möglich von der Funktion zurück
40
JohnFx

Ich weiß, dass einige Leute argumentieren werden, dass es ein Goto ist, aber return; ist das offensichtliche Refactoring, d.h.

if (!Check#1)
{ 
        return;
}
CodeBlock#1
if (!Check#2)
{
    return;
}
CodeBlock#2
.
.
.
if (Check#7)
{
    CodeBlock#7
}
else
{
    rest - of - the - program
}

Wenn es sich wirklich nur um eine Reihe von Schutzprüfungen handelt, bevor der Code ausgeführt wird, funktioniert dies einwandfrei. Wenn es komplizierter ist, wird es nur ein bisschen einfacher, und Sie benötigen eine der anderen Lösungen.

26
Richard Gadsden

Dieser Spaghetti-Code scheint der perfekte Kandidat zu sein, um ihn in eine Zustandsmaschine umzuwandeln.

10
Pemdas

Dies wurde vielleicht bereits erwähnt, aber meine "go-to" (Wortspiel beabsichtigt) Antwort auf das hier gezeigte "Pfeilspitzen-Antimuster" besteht darin, das Wenn umzukehren. Testen Sie das Gegenteil der aktuellen Bedingungen (einfach genug mit einem Nicht-Operator) und kehren Sie von der Methode zurück, wenn dies zutrifft. Keine Gotos (obwohl pedantisch gesprochen, ist eine nichtige Rückkehr kaum mehr als ein Sprung in die Zeile des aufrufenden Codes, mit dem trivialen zusätzlichen Schritt, einen Frame vom Stapel zu werfen).

Ein typisches Beispiel: Hier ist das Original:

if (Check#1)
{
    CodeBlock#1
    if (Check#2)
    {
        CodeBlock#2
        if (Check#3)
        {
            CodeBlock#3
            if (Check#4)
            {
                CodeBlock#4
                if (Check#5)
                {
                    CodeBlock#5
                    if (Check#6)
                    {
                        CodeBlock#6
                        if (Check#7)
                        {
                            CodeBlock#7
                        }
                        else
                        {
                            rest - of - the - program
                        }
                    }
                }
            }
        }
    }
}

Überarbeitet:

if (!Check#1) return;

CodeBlock#1

if (!Check#2) return;

CodeBlock#2

if (!Check#3) return;

CodeBlock#3

if (!Check#4) return;

CodeBlock#4

if (!Check#5) return;

CodeBlock#5

if (!Check#6) return;

CodeBlock#6

if (Check#7)
    CodeBlock#7
else
{
    //rest of the program
}

Bei jedem Wenn prüfen wir grundsätzlich, ob wir fortfahren sollen. Es funktioniert genauso wie das Original mit nur einer Verschachtelungsebene.

Wenn über das Ende dieses Snippets hinaus etwas ausgeführt werden sollte, extrahieren Sie diesen Code in eine eigene Methode und rufen Sie ihn von jedem Ort aus auf, an dem sich dieser Code derzeit befindet, bevor Sie mit dem Code fortfahren, der nach diesem Snippet kommen würde. Das Snippet selbst ist lang genug, wenn in jedem Codeblock genügend LOC vorhanden ist, um die Aufteilung mehrerer weiterer Methoden zu rechtfertigen, aber ich schweife ab.

6
KeithS

Wenn Sie routinemäßig eine Logik haben, die tatsächlich diese Pyramide von if Prüfungen erfordert, verwenden Sie wahrscheinlich (metaphorisch) einen Schraubenschlüssel, um Nägel zu hämmern. Es wäre besser, wenn Sie diese Art von verdrehter, komplizierter Logik in einer Sprache ausführen würden, die diese Art von verdrehter und komplizierter Logik mit besseren Strukturen unterstützt als lineare if/else if/else- Konstrukte.

Zu den Sprachen, die für diese Art von Struktur möglicherweise besser geeignet sind, gehören SNOBOL4 (mit seiner bizarren Verzweigung im Dual-GOTO-Stil) oder logische Sprachen wie Prolog und Mercury (mit ihren Vereinigungs- und Rückverfolgungsfähigkeiten, ganz zu schweigen von ihren DCGs für einen ziemlich prägnanten Ausdruck komplizierter Entscheidungen) .

Wenn dies keine Option ist (da die meisten Programmierer tragischerweise keine Polyglotten sind), sind die Ideen, die andere entwickelt haben, natürlich gut wie die Verwendung verschiedener OOP - Strukturen oder Aufteilen der Klauseln in Funktionen oder sogar, wenn Sie verzweifelt sind und es Ihnen nichts ausmacht, dass die meisten Leute sie mit einem Zustandsmaschine .

Meine Lösung besteht jedoch weiterhin darin, nach einer Sprache zu greifen, mit der Sie die Logik, die Sie ausdrücken möchten (vorausgesetzt, dies ist in Ihrem Code üblich), einfacher und besser lesbar ausdrücken können.

Von hier :

Sehr oft, wenn ich mir eine Reihe von Pac-Man-Ifs anschaue, stelle ich fest, dass ich einen viel besseren Weg zur Lösung des Problems finden kann, wenn ich nur so etwas wie eine Wahrheitstabelle aller damit verbundenen Bedingungen zeichne.

Auf diese Weise können Sie auch beurteilen, ob es eine bessere Methode gibt, wie Sie sie möglicherweise weiter aufschlüsseln und (und dies ist eine große Methode mit dieser Art von Code), ob die Logik Lücken aufweist.

Wenn Sie dies getan haben, können Sie es wahrscheinlich in ein paar switch-Anweisungen und ein paar Methoden aufteilen und dem nächsten armen Mook, der den Code durchgehen muss, eine Menge Probleme ersparen.

1
Jim G.

Persönlich mag ich es, diese if-Anweisungen in separate Funktionen zu verpacken, die einen Bool zurückgeben, wenn die Funktion erfolgreich ist.

Die Struktur sieht wie folgt aus:


if (DoCheck1AndCodeBlock1() && 
    DoCheck2AndCodeBlock2() && 
    DoCheck3AndCodeBlock3()) 
{
   // ... you may perform the final operation here ....
}

Der einzige Nachteil besteht darin, dass diese Funktionen Daten mithilfe von Attributen ausgeben müssen, die von Referenz- oder Elementvariablen übergeben werden.

1
gogisan

Wenn Sie ein OO) verwenden, nähern Sie sich einem zusammengesetzten Muster, bei dem Blatt eine einfache Bedingung und Komponente ist. Eine Vereinigung einfacher Elemente macht diese Art von Code erweiterbar und anpassbar

1
guiman

Wenn Sie eine mögliche objektorientierte Lösung wünschen, sieht der Code wie ein kanonisches Beispiel für das Refactoring mit dem Entwurfsmuster Chain of Responsibility aus.

0
FinnNk

Es könnte hilfreich sein, sie in Funktionen aufzuteilen.

Sie rufen die erste Funktion auf und wenn sie abgeschlossen ist, wird die nächste aufgerufen. Als Erstes würde die Funktion die Prüfung für diese Funktion testen.

0
Toby

Es hängt sehr von der Größe jedes Codeblocks und der Gesamtgröße ab, aber ich neige dazu, entweder durch eine gerade "Extraktionsmethode" für einen Block oder durch Verschieben der bedingungslosen Teile in separate Methoden zu teilen. Es gelten jedoch die genannten Vorbehalte @KeesDijk. Der erstere Ansatz bietet Ihnen eine Reihe von Funktionen, die Sie jeweils mögen

if (Check#1)
{
    CodeBlock#1
    NextFunction
}

Das kann gut funktionieren, führt aber zu aufgeblähtem Code und dem Antipattern "Methode nur einmal verwendet". Daher bevorzuge ich im Allgemeinen diesen Ansatz:

if (CheckFunctionOne)
{
    MethodOneWithDescriptiveName
    if (CheckFunctionTwo)
    {
        MethodTwoWithDescriptiveName
        ....

Bei entsprechender Verwendung privater Variablen und Parameterübergabe kann dies erfolgen. Ein Blick auf die Alphabetisierung kann hier helfen.

0
Мסž