Style-Frage bezüglich verketteten if-else-Konstrukte



  • Hallo,

    dem im Folgenden vorgestellten Fall laufe ich mindestens einmal pro Monat über den Weg:

    void f()
    {
    	bool Match = true;
    	if( /* ... */ )
    	{
    		// ...
    	}
    	else if( /* ... */ )
    	{
    		// ...
    	}
    	/*
    	 *
    	 */
    	else if( /* ... */ )
    	{
    		// ...
    	}
    	else
    	{
    		Match = false;
    	}
    
    	if( Match )
    	{
    		// ...
    	}
    }
    

    Im Code ist bereits eine mögliche Problemlösung implementiert. Grundsätzlich will ich einen bestimmten Code ausführen, wenn mindestens eine Bedingung aus einer if-else-Kette gegriffen hat. Meine Lösung mit einem Flag, wie im Code gezeigt, gefällt mir nicht sonderlich wegen besagtem Flag. Besonders schmerzhaft ist das dann, wenn ich nur 2 if s habe. Dann braucht der Flag-Mechanismus fast soviele Zeilen wie der Rest.

    Gibt es für dieses Problem eine "Standard-Lösung"?



  • void f()
    {
    	if( /* ... */ )
    	{
    		// ...
    	}
    	else if( /* ... */ )
    	{
    		// ...
    	}
    	/*
    	 *
    	 */
    	else if( /* ... */ )
    	{
    		// ...
    	}
    	else
    	{
    		return;
    	}
    
    	// ...
    }
    


  • Ups, da hätte ich im Beispiel keine Funktion machen sollen. So sollte das dann aussehen:

    bool Match = true;
    if( /* ... */ )
    {
    	// ...
    }
    else if( /* ... */ )
    {
    	// ...
    }
    /*
     *
     */
    else if( /* ... */ )
    {
    	// ...
    }
    else
    {
    	Match = false;
    }
    
    if( Match )
    {
    	// ...
    }
    

    Ist nicht immer (realistisch) möglich, das in eine einzelne Funktion zu packen.



  • stylahrr schrieb:

    Ist nicht immer (realistisch) möglich, das in eine einzelne Funktion zu packen.

    *eigene



  • Doch!
    Wenn eine Funktion länger als 20-30 Codezeilen wird, dann sollte man sie in kleinere Funktionen zerlegen.

    bool match = CanMatch();
    if (match)
    {
        // ...
    }
    


  • Th69 schrieb:

    Doch!
    Wenn eine Funktion länger als 20-30 Codezeilen wird, dann sollte man sie in kleinere Funktionen zerlegen.

    Und dann an jede Unterfunktion die 10+ lokalen Variablen übergeben oder was?
    Ich sehe das gleich, dass man i.d.R. alles in knappe und prägnante Funktionen aufteilen sollte, aber ab und zu ist eine einzelne Funktion mit meinetwegen 200 Zeilen einfacher als 20 Funktionen mit je 10+ Parametern und langen, kryptischen Namen, die ansonsten nirgends aufgerufen werden (können).



  • Natürlich sollst du nicht 10+ Parameter haben. Für mich ist die Grenze des Zumutbaren so etwa bei 6 bis 7 Parametern.

    Jedenfalls ist sicher, dass 200 Codezeilen aber zu viel für eine Funktion sind.

    Ohne dein genaues Problem zu kennen, würde ich mal stark vermuten, dass es sich irgendwie anders besser lösen lassen sollte. Kannst du den konkreten Fall genauer beschreiben?



  • Man könnte die lokalen Variablen ja in einer struct zusammenfassen und die dann übergeben. Dann hat man nur noch einen Parameter. Allerdings auch mehr Indirektionen. Oder man bastelt sich gleich eine Klasse. oder...



  • stylahrr schrieb:

    Th69 schrieb:

    Doch!
    Wenn eine Funktion länger als 20-30 Codezeilen wird, dann sollte man sie in kleinere Funktionen zerlegen.

    Und dann an jede Unterfunktion die 10+ lokalen Variablen übergeben oder was?
    Ich sehe das gleich, dass man i.d.R. alles in knappe und prägnante Funktionen aufteilen sollte, aber ab und zu ist eine einzelne Funktion mit meinetwegen 200 Zeilen einfacher als 20 Funktionen mit je 10+ Parametern und langen, kryptischen Namen, die ansonsten nirgends aufgerufen werden (können).

    Method Object to the rescue.
    MMn. ein sehr wichtiges Pattern das viel zu wenig bekannt ist und viel zu wenig verwendet wird.

    ps: Kennt jemand ne bessere Beschreibung des Method Object Patterns? Auf Wikipedia finde ich das Pattern nicht, was ich für seltsam halte. Oder hat das vielleicht noch nen anderen Namen?



  • hustbaer schrieb:

    Method Object to the rescue.
    MMn. ein sehr wichtiges Pattern das viel zu wenig bekannt ist und viel zu wenig verwendet wird.

    Habe leider keinen anderen Namen parat, aber das Pattern hat mich an einen Vortrag von Chandler Carruth zum Thema Compiler-Optimierungen erinnert, der dich auch interessieren könnte:
    https://www.youtube.com/watch?v=FnGCDLhaxKU&t=1h32m10s
    (Link ist auf die Stelle im Video wo das Pattern - wenn ich das nicht missverstanden habe - zur Sprache kommt).

    Scheinbar haben einige moderne Compiler arg damit zu kämpfen so etwas zu optimieren.
    Stattdessen ein struct an eine freie/statische Funktion zu übergeben scheint es dem Compiler leichter zu machen.

    Gruss,
    Finnegan



  • Ja, danke, der Vortrag ist interessant.

    Einige Gedanken dazu:

    * Ich glaube das was man da an Performance verliert wird in den allermeisten Fällen keine Rolle spielen.

    * In dem Beispiel in dem von dir verlinkten Vortrag wird die struct by value übergeben. Das geht manchmal, aber oft genug funktioniert das Method Object Pattern damit nicht mehr - bzw. nicht mehr vernünftig. Einer der grossen Vorteile von Method Object ist ja gerade dass man mutable Member hat.

    * Natürlich macht es keinen Sinn die Realität zu ignorieren (=also dass Compiler solche Funktionen u.U. nicht gut optimieren können). Wenn man also unbedingt ideale Performance an einer Stelle braucht, dann sollte man solche Konstrukte an dieser Stelle auch vermeiden. I.A. finde ich macht es allerdings auch wenig Sinn zu viel Rücksicht auf den Compiler zu nehmen. Wenn Compiler das (noch) nicht so gut können, dann ist das bloss ein Punkt wo die Compiler noch deutlich was aufzuholen haben. Und das sollen sie dann bitte tun. D.h. an allen Stellen wo das letzte Bisschen Performance nicht so wichtig ist, programmiere ich so wie es praktisch für mich bzw. die anderen beteiligten Softwareentwickler ist, und nicht so wie es praktisch für den Compiler ist.



  • hustbaer schrieb:

    * Ich glaube das was man da an Performance verliert wird in den allermeisten Fällen keine Rolle spielen. [...]

    Da schliesse ich mich an. Ich würde meinen Beitrag auch nur ungerne als Plädoyer gegen dieses Pattern missverstanden wissen - er war eher informativ gedacht,
    weil ich wie wohl auch andere intuitiv vermutet hätten, dass sich so etwas durchaus gut optimieren lässt. Ausserhalb von performancekitischen Codepfaden würde
    ich auch die verständlichere Variante, bzw. die elegantere Abstraktion bevorzugen - nicht zuletzt um nicht in ein paar Jahren eine Codebase zu haben, die altbacken
    rüberkommt wie heute diverser Code aus Zeiten schlechterer Compiler (manuelles Loop-Unrolling, oder Assembler-Passagen die heute besser und vor allem
    CPU-unabhängiger vom Compiler generiert werden könnten).

    Es ist jedenfalls nicht verkehrt immer zuerst direkte und simple Lösungen zu bevorzugen (die Philosophie von C++ hat sich ja in den letzten Jahren stark in
    diese Richtung gewendet). Wenn der Code dem entspricht wo die Reise hingehen soll, werden die Compiler sicher in Zukunft besser mit sowas klarkommen
    (was standen einem vor vielen Jahren noch die Haare zu Berge wenn die Anfänger so simpel und geradeaus programmiert haben - so viel "by value" - die
    ganzen überflüssigen Kopien und so! Heute gilt das oft als guter Code, während der gute Stil von damals nicht mehr so toll aussieht ;))

    Finnegan



  • Ich hatte mir das Video auch irgendwann mal angeschaut. Und was mir z.B. fehlt, oder vielleicht ist es einfach nur an mir vorbeigegangen, wäre die Info, was der Compiler hier konkret überhaupt optimieren könnte. Es kam mehrmals die Aussgage, dass z.B. die Übergabe by value besser für Optimierungen wäre, mir ist aber nicht wirklich klar, ob der Compiler hier überhaupt etwas optimieren könnte.


Anmelden zum Antworten