Direkt zum Hauptbereich

Ohne Wenn und Aber


Der Statistik nach scheint "Code Retreat: Keine If-Ausdrücke" der am häufigsten gelesene Artikel dieses Blogs zu sein (Wie geht das, bei nur zwei Lesern?). Dabei enthält der Artikel nur ein unsinniges Anti-Pattern und ist damit nicht sehr hilfreich.

In diesem Artikel möchte ich deswegen etwas nützlichere Informationen geben. Ich wechsle dabei in den Beispielen zwischen C#- und Python-Code hin und her :-)

Natürlich sind bedingte Verzweigungen (if, switch, ?-Operator) nicht an sich schlecht. Allerdings kann es Alternativen zu ihrer Verwendung geben, die den Code übersichtlicher und verständlicher machen. Ob das in einem konkreten Fall wirklich so ist, muss dann situativ entschieden werden.

Fall 1: Desine decision vs. Runtime decision

Einige Ifs können manchmal schon entfernt werden, weil sich herausstellt, dass die Fallunterscheidungsinformationen bereits zur Zeit der Programmierung und nicht erst zur Laufzeit zur Verfügung steht.

Mit If:
File Open(string path, string accessType)
{
    if (accessType == "w")
    {
        //Open file for write access
        //and return file descriptor
    }
    if (accessType == "r")
    {
        //Open file for read access
        //and return file descriptor
    }
    //Unknown access type
    return null;
}

void main()
{
    File dataFile = Open("data.txt", "r");
    //read data
}

Ohne If:
File OpenWrite(string path)
{
    //Open file for write access
    //and return file descriptor
}

File OpenRead(string path)
{
    //Open file for read access
    //and return file descriptor
}


void main()
{
    File dataFile = OpenRead("data.txt");
    //read data
}

Fall 2: Constant-Member

Wenn die Fallunterscheidung aufgrund einer Instanzvariablen erfolgt, die einmalig im Konstruktor gesetzt und ansonsten nur gelesen wird, ist die Aufteilung in mehrere Unterklassen wahrscheinlich die bessere Lösung.

Mit If:
class ConversationBot:
    def __init__(self, formal):
        self.formal = formal
        
    def printGreeting(self):
        if self.formal:
            print("Good morning")
        else:
            print("Hi")
            
    def printFarewell(self):
        if self.formal:
            print("Goodbye")
        else:
            print("Peace")

Ohne If:
class FormalConversationBot:
    def printGreeting(self):
        print("Good morning")
            
    def printFarewell(self):
        print("Goodbye")

class CasualConversationBot:
    def printGreeting(self):
        print("Hi")
            
    def printFarewell(self):
        print("Peace")

Fall 3: State-Member

Ändert sich die Variable während der Laufzeit - und damit auch das bedingte Verhalten - würde sich das Strategie- oder Zustands-Pattern als Alternative anbieten (wiewohl mir das Zustands-Pattern nicht zusagt und ich eher eine klassische Zustandstabelle bevorzuge).

Mit If:
puplic class CDPlayer
{
    private string state;

    public CDPlayer()
    {
        this.state = "CLOSED";
    }

    public void EjectButtonPushed()
    {
        if (this.state == "CLOSED")
        {
            //Do stuff to open the device
            this.state = "OPEN";
        }
        else
        {
            //Do stuff to close the device
            this.state = "CLOSED"
        }
    }
}

Ohne If:
puplic class CDPlayer
{
    public DeviceState state;

    public CDPlayer()
    {
        this.state = new ClosedState();
    }

    public void EjectButtonPushed()
    {
        this.state.EjectButtonPushed(this);
    }
}

public interface DeviceState
{
    void EjectButtonPushed(CDPlayer player);
}

public class OpenState : DeviceState
{
    public void EjectButtonPushed(CDPlayer player)
    {
        //Do stuff to close the device
        player.state = new ClosedState();
    }
}

public class CloseState : DeviceState
{
    public void EjectButtonPushed(CDPlayer player)
    {
        //Do stuff to open the device
        player.state = new OpenState();
    }
}

Fall 4: Dictionary

Statt eines switch-Ausdrucks könnte auch ein dictionary verwendet werden, um Werte aufeinander zu mappen (wobei Werte auch Funktionen sein können), was die Möglichkeit eröffnet, solche Mappings aus einer Konfigurationsdatei zu laden.

Mit If:
def getAreaCode(landCode):
    if landCode == "FR":
        return "+33"
    if landCode == "GB":
        return "+44"
    if landCode == "PL":
        return "+48"
    if landCode == "DE":
        return "+49"
    return None

Ohne If:
landToArea = {
    "FR": "+33"
    "GB": "+44"
    "PL": "+48"
    "DE": "+49"
}

def getAreaCode(landCode):
    try:
        return landToArea[landCode]
    except Exception:
        return None

Fall 5: Mathematic solution

Man könnte sich außerdem überlegen, ob eine Wertetransformation auch durch eine mathematische oder logische Funktion erfolgen kann.

Mit If:
string getTimeDependingGreeting(int hour)
{
    if (hour < 6)
    {
        return "Gute Nacht!";
    }
    if (hour >= 6 && hour < 12)
    {
        return "Guten Morgen!";
    }
    if (hour >= 12 && hour < 18)
    {
        return "Guten Tag!";
    }
    return "Guten Abend!";
}

Ohne If:
string getTimeDependingGreeting(int hour)
{
    var greetings = new [] {"Gute Nacht!",
                            "Guten Morgen!",
                            "Guten Tag!",
                            "Guten Abend!"}
    return greetings[hour / 6];
}

Wer noch andere Wege zur If-Vermeidung kennt, teile diese mir bitte mit!

Kommentare

  1. In Python gibt es ja keine Switch Anweisung und man verwendet da dann oftmals das von dir beschriebene Vorgehen mit einem Dictionary. Manchmal ist das gut, manchmal eher schwierig, insbesondere dann, wenn das Dictionary viel weiter oben im Code definiert wurde und am Entscheidungspunkt nicht sichtbar ist. Dann beginnt die fröhliche Scrollerei.

    Auch wenn dein Beispiel 3 natürlich nur ein Beispiel ist, so führt es doch schnell zu einem Wust an Klassen. Und wehe, dir fällt 2 Wochen später ein weiterer else-if Zweig ein. Dann kannst du wieder alle Klassen umbenennen :-)

    Wie dem auch sei, sehr gute Beispiele!

    AntwortenLöschen
  2. Ich sehe in Python als Ersatz für Switches sehr oft lange folgen von elifs:
    if value==1:
    #Do 1
    elif value==2:
    #Do 2
    elif value==3:
    #Do3
    ...

    Um die von Dir kritisierte Scrollerei einzuschränken, sollte sich das Dictionary und die entsprechenden Zugriffsmethoden in einer hoch kohäsiven Klasse befinden und damit nur eine Leerzeile voneinander entfernt sein :-). Anonsten kommt es natürlich darauf an, welche Daten aufeinander gemappt werden und ob und warum der Leser des Codes, den Inhalt des Mappings kennen muss. Evtl. ein Hinweis, dass da noch mehr im Argen liegt.

    Was das Zustands-Pattern angeht: Ja, hier stört mich auch die Unübersichtlichkeit, die durch die vielen Klassen entsteht. Deswegen bevorzuge ich in solchen Fällen auch eher Zustandstabellen, weil hier die Informationen an einer Stelle vereint sind und trotzdem von der Ablauflogik getrennt.

    AntwortenLöschen

Kommentar veröffentlichen

Beliebte Posts aus diesem Blog

Utopie gesucht

In den 90er Jahren gab es meiner Meinung nach eine positive Zukunftssicht. Das sah man u.a. in der Serie Star Trek The next generation. Heute dagegen scheint es nur noch pessimistische Blicke auf die Zukunft zu geben. Auch die aktuellen Star Trek Serien stellen eine düsterere Welt dar. Dies könnte zu einer selbst erfüllenden Prophezeiung werden. Gibt es in der aktuellen Popkultur noch Utopien?

Sächsisch vs. Sachsen-Anhalter Mundart

I Want To Hold Your Hand

Ich habe gerade im Radio gehört, dass genau heute vor 60 Jahren die Beatles ihr Stück I Want To Hold Your Hand veröffentlicht haben. Dafür unterbreche ich gerne meine Blog-Abstinenz und zeige hier meine Lieblingsinterpretation dieses Lieds aus der wunderbaren Serie GLEE: