Verschachtelte redundante ‘if’-Bedingungen

Lesezeit: 9 Minuten

Benutzer-Avatar
HAL

Gibt es eine bessere (oder sauberere) Möglichkeit, den folgenden Code zu schreiben?

if(conditionX)
{
    if(condition1)
    {
        // code X1
    }
    else if(condition2)
    {
        // code X2
    }
}
else if(conditionY)
{
    if(condition1)
    {
        // code Y1
    }
    else if(condition2)
    {
        // code Y2
    }
}

Ich habe noch ein paar Bedingungen, aber ich denke, Sie verstehen, worauf es ankommt.

  • Nicht genug Informationen für mich. Wenn es eine Möglichkeit gibt, Ihr Ziel zu erreichen, verwenden Sie besser eine Aufzählung. Wie kommt es jedoch zu so vielen Upvotes für die Frage (O_o)~!

    – Sadik

    18. März 2014 um 12:23 Uhr


  • Ich sehe keine überflüssig ifist da.

    – Karoly Horvath

    20. März 2014 um 17:45 Uhr

  • Bitte nichts dagegen, aber -1, weil ich hier keine wirkliche Frage sehe.

    – ST3

    26. März 2014 um 11:35 Uhr

Benutzer-Avatar
Sergej Kalinitschenko

Es gibt vier Ansätze für dieses Problem, von denen keiner universell ist:

  1. Alles so lassen wie es ist – Hier gibt es nicht viel Codeduplizierung. Wenn es ums Rechnen geht condition1 und condition2 ist schwierig, berechnen Sie sie im Voraus und speichern Sie sie in bool Variablen
  2. Machen conditionX und conditionY ein Ergebnis produzieren, das Sie vereinheitlichen lässt condition1 und condition2 – Dies ist nicht immer möglich, aber in einigen Situationen könnten Sie eine Variable vorbereiten, die die in den beiden Zweigen durchgeführten Aktivitäten vereinheitlicht, z. B. durch Verwendung eines Funktionszeigers oder eines Lambda.
  3. Unterteilen Sie die Verarbeitungslogik in Unterklassen mit virtuellen Funktionen, um bedingte Logik zu eliminieren – Dies ist nur möglich, wenn Ihr ursprüngliches Design eine Gelegenheit zur Unterklasse verpasst hat. Im Wesentlichen schiebt diese Vorgehensweise die Entscheidung auf conditionX/conditionY an einen Ort, an dem eine Unterklasse erstellt wird, und diese Entscheidung später “wiederverwendet”, indem eine ordnungsgemäße Überschreibung einer virtuellen Funktion in der Schnittstelle aufgerufen wird.
  4. Erstellen Sie eine numerische Kombination, die alle drei Bedingungen darstellt, und konvertieren Sie in switch – Dieser Trick vereinheitlicht die Bedingungen und reduziert die Verschachtelung.

Hier ist ein Beispiel für den letzten Ansatz:

int caseNumber = ((conditionX?1:0) << 3)
               | ((conditionY?1:0) << 2)
               | ((condition2?1:0) << 1)
               | ((condition1?1:0) << 0);
switch (caseNumber) {
    case 0x09:
    case 0x0D:
    case 0x0F: // code X1
        break;
    case 0x0A:
    case 0x0E: // code X2
        break;
    case 0x05:
    case 0x07: // code Y1
        break;
    case 0x06: // code Y2
        break;
}

  • Es sei denn, die caseNumbers bedeuten etwas, der dritte Ansatz ist deutlich schlechter als das Original. IMVHO zumindest.

    – Vonbrand

    18. März 2014 um 12:36 Uhr


  • @vonbrand Der letzte Ansatz ist nützlich für Situationen, in denen Sie alle oder fast alle Kombinationen abdecken müssen. Eine Kette von ifs ist mit nur vier Bedingungen in Ordnung, aber sobald Sie fünf oder sechs erreicht haben, ist eine flache Struktur mit bedeutungslosen Zahlen und vielen Kommentaren aus Gründen der Lesbarkeit besser als eine Mehrfachverschachtelung.

    – Sergej Kalinitschenko

    18. März 2014 um 12:41 Uhr

  • Ich stimme zu, dass der Wechsel besser sein kann als tief verschachtelte, sich wiederholende Bedingungen, aber ich stimme auch zu, dass Sie aussagekräftige Fallbezeichnungen wünschen. Sehen Sie sich die Antwort von Barak Manos an, um die Fälle besser darzustellen, damit Sie sehen können, welchen Bedingungen sie entsprechen

    – Jonathan Wakely

    18. März 2014 um 12:51 Uhr


  • @dasblinkenlight: Sie behandeln nicht Fall 0x7 (Code-Y1), Fall 0xB (Code-X1), Fall 0xD (Code-X1), Fall 0xE (Code-X2) und Fall 0xF (Code-X1). All dies ist darauf zurückzuführen, dass conditionX == true bedeutet das nicht unbedingt conditionY == false, und umgekehrt. Dasselbe gilt für condition1 und condition2.

    – barak manos

    18. März 2014 um 13:15 Uhr


Wenn Sie sich Sorgen um sauberen Code in Bezug auf die Anzeige der Quelle machen, wäre mein Rat, die Blöcke in ihre eigenen Abschnitte zu unterteilen, etwa so:

if      (conditionX) processConditionX();
else if (conditionY) processConditionY();

usw.

Dann platzierst du in den Unterfunktionen das „Fleisch“:

void processConditionX (void) {
    if(condition1) {
        // code X1
    } else if(condition2) {
        // code X2
    }
}

Sie können es ändern, um Parameter nach Bedarf zu übergeben und zurückzugeben, und ich würde die Bedingungen und Funktionsnamen etwas aussagekräftiger machen, obwohl ich annehme, dass es sich hier nur um Beispiele handelt.

Benutzer-Avatar
barak manos

Sie können stattdessen eine Zustandsmaschine implementieren:

#define COMBINATION(a,b,c,d) (((a)<<3)|((b)<<2)|((c)<<1)|((d)<<0))

switch (COMBINATION(conditionX,conditionY,condition1,condition2))
{
    case COMBINATION(0,0,0,0):           break;
    case COMBINATION(0,0,0,1):           break;
    case COMBINATION(0,0,1,0):           break;
    case COMBINATION(0,0,1,1):           break;
    case COMBINATION(0,1,0,0):           break;
    case COMBINATION(0,1,0,1): CodeY2(); break;
    case COMBINATION(0,1,1,0): CodeY1(); break;
    case COMBINATION(0,1,1,1): CodeY1(); break;
    case COMBINATION(1,0,0,0):           break;
    case COMBINATION(1,0,0,1): CodeX2(); break;
    case COMBINATION(1,0,1,0): CodeX1(); break;
    case COMBINATION(1,0,1,1): CodeX1(); break;
    case COMBINATION(1,1,0,0):           break;
    case COMBINATION(1,1,0,1): CodeX2(); break;
    case COMBINATION(1,1,1,0): CodeX1(); break;
    case COMBINATION(1,1,1,1): CodeX1(); break;
}

Dies beinhaltet nur eine Verzweigungsoperation, ist also möglicherweise etwas effizienter (obwohl es auch eine zusätzliche Laufzeitberechnung (bei der switch Linie)).

Ich denke, es ist eine Frage der Perspektive, sauberer zu sein, aber die obige Vorlage bietet Ihnen auch eine bequeme Möglichkeit, alle unbehandelten Zweige in Ihrem Code zu erkennen.

Bitte beachten Sie, dass ggf irgendein der Bedingungsvariablen kann einen anderen Wert als 1 oder 0 haben, dann sollten Sie:

#define COMBINATION(a,b,c,d) (((a)?8:0)|((b)?4:0)|((c)?2:0)|((d)?1:0))

Update (zugeschrieben @Jonathan Wakely in einem der Kommentare unten):

Wenn Sie C++11 verwenden, können Sie die ersetzen COMBINATION Makro mit a constexpr Funktion:

constexpr int COMBINATION(bool a,bool b,bool c,bool d)
{
    return ((int)a<<3) | ((int)b<<2) | ((int)c<<1) | ((int)d<<0);
}

  • COMBINATION sollte eine constexpr-Funktion sein, kein Makro

    – Jonathan Wakely

    18. März 2014 um 12:47 Uhr

  • @Jonathan Wakely: Warum? Ich würde es vorziehen, während der Kompilierzeit (oder Vorverarbeitungszeit) so viele Berechnungen wie möglich zu haben.

    – barak manos

    18. März 2014 um 12:49 Uhr


  • Ja. Das ist, was ein constexpr Funktion ist für. Makros sind scheiße. In C haben Sie leider keine Wahl, aber in C++11 ist eine constexpr-Funktion aus vielen Gründen vorzuziehen (klarer, typsicherer, mit C++-Syntax geschrieben, nicht mit dem fehleranfälligen Präprozessor). Zum Beispiel wenn conditionX ist ein int mit dem Wert 15 macht Ihr Makro das Falsche. constexpr condX(bool b) { return int(b) << 3; } Hätte das Problem nicht, wird der Wert umgerechnet true zuerst, kann dann erst nach dem Verschieben den richtigen Wert erzeugen

    – Jonathan Wakely

    18. März 2014 um 12:53 Uhr


  • @Jonathan Wakely: Danke, dass du mir etwas beigebracht hast, was ich nicht wusste 🙂 … OP hat diese Frage als markiert C und C++, daher bin ich mir nicht sicher, welches verwendet wird. Ich habe meiner Antwort (basierend auf Ihrer Beschreibung) auf jeden Fall ein Update hinzugefügt …

    – barak manos

    18. März 2014 um 13:08 Uhr


Benutzer-Avatar
CloudyMarble

Ich würde die Entscheidung im ersten if als Parameter für eine separate Funktion bereitstellen, die dann entscheidet, welcher Code ausgeführt werden soll, wie:

if(conditionX)
{
    Method1(Condition Parameters)
}
else if(conditionY)
{
    Method1(Condition Parameters)
}

Eine andere Möglichkeit wäre, alle erforderlichen Informationen einer Entscheidungsmethode (Matrix) bereitzustellen. Diese Methode gibt eine ganze Zahl zurück, die Sie in einer switch-Anweisung verwenden, um zu entscheiden, welcher Code ausgeführt werden soll. Auf diese Weise trennen Sie die Entscheidungslogik, die sie lesbar und bei Bedarf leicht zu testen macht:

DecisionMatrix(conditionX, conditionY, condition1, condition2)
{
  //  return a value according to the conditions for Example:
  // CoditionX + Condition1 => return 1
  // CoditionX + Condition2 => return 2
  // CoditionY + Condition1 => return 3
  // CoditionY + Condition2 => return 4
}

switch(DecisionMatrix)
{
    case 1: //run code X1       
    break;
    case 2: //run code X2
    break;
    case 3: //run code Y1       
    break;
    case 4: //run code Y2
    break;
}

  1. Der beste Weg hier wäre, Polymorphismus zu verwenden (Nur wenn die Code-Blöcke riesig sind)

  2. Wenn es sich um kleine Codeschnipsel handelt, wäre das Erstellen von Klassen offensichtlich ein Overkill.

    Wenn also alle Codes ähnlich sind, würde ich eine scheinbar einfache, aber wirklich schwierige Aufgabe vorschlagen.

    • Versuchen Sie, sie so weit wie möglich zu parametrisieren.
    • Erstellen Sie eine Funktion, die diese übernimmt, und rufen Sie sie in den Bedingungen auf
    • Jetzt wäre der Code in Funktionsblöcken und “sauberer”

Es ist immer schwierig, einfache Dinge zu erschaffen.

if (conditionX) {
    method(parameterX);
else if (conditionY) {
    method(parameterY);
}

wo

void method(ParameterType e) {
    if (condition 1) {
        // Code in terms of parameter e
    } else if (condition2) {
        // Code in terms of parameter e
    }
}

Die parametrierbare Bedingung sollte außen vor bleiben.

Hoffe das hilft.

Benutzer-Avatar
Hyun

Ich denke, dieser Weg kann ein weiterer Weg sein, um Ihren Code zu lösen.

enum ConditionParentType
{
    CONDITION_NONE = 0,
    CONDITION_X,
    CONDITION_Y,
};

enum ConditionChildType
{
    CONDITION_0 = 0,
    CONDITION_1,
    CONDITION_2,
};

class ConditionHandler
{
public:
    explicit ConditionHandler(ConditionParentType p_type, ConditionChildType c_type) 
        : p_type_(p_type), c_type_(c_type) {};
    void DoAction()
    {
        if(child_type == CONDITION_1)
        {

        }
        else if(child_type == CONDITION_2)
        {

        }
        else
        {
            //error
        }
    }

private:
    const ConditionParentType p_type_;
    const ConditionChildType  c_type_;
}; 

int main(int argc, char *argv[]) 
{
    ConditionParentType parent_type = GetParentType();
    ConditionChildType  child_type  = GetChildType();

    ConditionHandler handler(parent_type, child_type);
    handler.DoAction();

    getchar();

    return 0;
}

Benutzer-Avatar
Sturmwolke

Wenn die Kombination von Bedingungen etwas bedeutet, würde ich eine Reihe einfacher Methoden schreiben, die boolesche Werte zurückgeben. Sie würden am Ende so etwas wie:

  if (first-condition(conditionX, condition1)) {
    // code X1
  } else if (first-condition(conditionX, condition2)) {
    // code X2
  } else if (third-condition(conditionY, condition1)) {
    // code Y1
  } else if (fourth-condition(conditionY, condition2)) {
    // code Y2
  }

Die Namen der Methoden beschreiben die Bedingungen. Machen Sie sich keine Sorgen, dass die Methoden nur einmal aufgerufen werden (der Compiler wird sie wahrscheinlich sowieso einbetten), wichtig ist, dass Ihr Code dann selbstdokumentierend wird.

  • Aber wenn der Zustand beispielsweise der 4. wäre, würde dies zu einer Leistungsminderung führen. Diese Methode von Ihnen hätte 4 if-Anweisungen durchlaufen, während die Methode des ursprünglichen Posters nur 3 if-Anweisungen durchlaufen würde. Es hört sich nicht viel an, aber wenn es weit mehr Bedingungen gäbe, könnte der Leistungsabfall erheblich sein.

    – Joe Harper

    19. März 2014 um 11:48 Uhr

  • Es kommt sehr darauf an, wie genau die Bedingungen sind. Ich gehe davon aus, dass dies ziemlich einfache Ausdrücke sein werden, die daher schnell auszuwerten sind. Dies mag nicht wahr sein (und die Frage sagt es nicht), aber es ist normalerweise der Fall. Bei dieser Frage klingt das Überspringen einer einzelnen Bedingung nach vorzeitiger Optimierung, die den Code oft langsamer machen kann! IMHO ist es weitaus besser, einen klaren Quellcode zu haben, da dies die Arbeit des Wartungsingenieurs (typischerweise ich!) einfacher und weniger fehleranfällig macht. Ich könnte weitermachen, aber ich komme vom Thema ab.

    – Sturmwolke

    19. März 2014 um 14:59 Uhr

1372800cookie-checkVerschachtelte redundante ‘if’-Bedingungen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy