Frage zum Stil



  • Welchen von den drei Stilen würdet Ihr jeweils als am besten ansehen und warum?

    Stil 1:

    bool Funktion()
    {
    	if (Bedingung)
    	{
    		TuWas;
    		TuMehr;
    
    		return true;
    	}
    	else
    		return false;
    }
    

    Stil2:

    bool Funktion()
    {
    	if (!Bedingung)
    		return false;
    	else
    	{
    		TuWas;
    		TuMehr;
    
    		return true;
    	}
    }
    

    Stil 3:

    bool Funktion()
    {
    	if (!Bedingung)
    		return false;
    
    	TuWas;
    	TuMehr;
    
    	return true;
    }
    

    ----------

    Stil 1:

    void Funktion()
    {
    	if (Bedingung)
    	{
    		TuWas;
    		TuMehr;
    	}
    	else
    		throw Exception("Fehler");
    }
    

    Stil 2:

    void Funktion()
    {
    	if (!Bedingung)
    		throw Exception("Fehler");
    	else
    	{
    		TuWas;
    		TuMehr;
    	}
    }
    

    Stil 3:

    void Funktion()
    {
    	if (!Bedingung)
    		throw Exception("Fehler");
    
    	TuWas;
    	TuMehr;
    }
    

    P.S.: Es geht nur darum, die Stile jeweils untereinander zu vergleichen. Es geht hier nicht um den Vergleich Rückgabewert vs. Exception.



  • Stil 3, vorausgesetzt die frühen returns sind deutlich gekennzeichnet. Das erleichtert die Lesbarkeit des Programmflusses. Soll heißen: wenn man Bedingungen hat die zu einem schnellen Ausstieg führen sollten die angeführt werden bevor der längere "Hauptzweig" der Ausführung kommt. Grade wenns mehrere solcher Ausstiegspunkte gibt, würd Version 2 eine tiefe Schachtelung ergeben (deshalb Hauptzweig nicht in else), und Version 1 eine tiefe Schachtelung plus einen schlechten Zusammenhang von Prüfung der Ausstiegsbedingung und Ausstiegspunkt. Bsp:

    Version 1

    int foo(char c)
    {
      if (! Ausstiegsbedingung1)
      { //Hauptzweig...
        bla();
        //blubb...
        std::cout << "Schoenes Wetter heute...\n";
        if (! Ausstiegsbedingung2)
        { //weiter im Text...
          int blubb = foobar(23);
          //weiteres gefrickel, wichtige Ausgaben und sowas..
    
          if(! Ausstiegsbedingung3)
          {
             //noch ein bisschen rechnen...
             int wuppdich = 17*blubb + 3;
             return wuppdich;
          }
          else return -1; //wofür war das else noch gleich?
        }
        else throw std::exception("Das wars dann wohl..."); //moment - die Exception kam durch welches if?
      }
      else return 0; // else wozu???? mal hochscrollen...
    }
    

    Version 3 dagegen:

    int foo(char c)
    {
      if (Ausstiegsbedingung1)
      {
        return 0;   //okay, weitere Berechnungen unnötig, also schluss
      }
    
      bla();
      //blubb...
      std::cout << "Schoenes Wetter heute...\n";
    
      if (Ausstiegsbedingung2)  //ah, ein Fehler, also Abbruch!
      {
        throw std::exception("Das wars dann wohl...");
      }
    
      int blubb = foobar(23);
      //weiteres gefrickel, wichtige Ausgaben und sowas..
    
      if(Ausstiegsbedingung3)
      {
        return -1;  //in dem Fall weiß man gleich was zurückgegeben werden muss also tut man das und spart sich den Rest...
      }
    
      //noch ein bisschen rechnen...
      int wuppdich = 17*blubb + 3;
      return wuppdich; //fertig :)
    }
    

    Viel übersichtlicher oder nicht?


  • Administrator

    Es kommt ein wenig auf die Situation drauf an. Wenn gleich zu Beginn gewissen Entscheidungen getroffen werden können, wodurch die Funktion bereits abbrechen kann, dann wähle ich den dritten Stil:

    bool function()
    {
      if(!Bedingung1 && !Bedingung2 /* && ... */)
      { return false; }
    
      /*
      Hier spare ich dadurch eine Einrückungsebene.
      Zudem habe ich so gleich die Pre-Bedingungen drin.
      */
    }
    

    Wenn die Sache allerdings etwas mehr verschachtelt ist und nur ein weg zum korrekten Ergebnis führt, dann verwende ich eine veränderte Form vom ersten Stil:

    bool function()
    {
      if(Bedingung1)
      {
        // ...
        if(Bedingung2)
        {
          // ...
          try
          {
             // ...
             return true;
          }
          catch(...)
          { /* ... */ }
        }
      }
    
      // Alles andere war falsch!
      return false;
    }
    

    Im allgemeinen verhalte ich mich einfach so, dass so viele return s wie nur möglich an den Beginn oder ans Ende der Funktion kommen.

    Edit:
    Noch als Ergänzung zum Post von pumuckl. Wenn ich So viele Abbruchbedingungen in der Mitte der Funktion habe, dann weisst es mir meistens an, dass ich zu wenig Funktionen habe. Ich lagere dann gerne mal gewisse Überprüfungen und solches aus der eigentlichen Funktion aus.

    Grüssli



  • Abgesehen davon, dass ich etwas anders notieren würde (kein if ohne geschweifte Klammern!) kann ich mich pumuckl nur anschließen.



  • _matze schrieb:

    (kein if ohne geschweifte Klammern!)

    Jo sorry, da hab ich gegen meine eigenen Konventionen verstoßen, aus purer Faulheit. Weils ein schlechtes Beispiel ist wirds sofort geändert 🙂



  • _matze schrieb:

    kein if ohne geschweifte Klammern!

    Ich handhabe das grundsätzlich so, dass ein if nur ohne geschweifte Klammern auskommen soll, wenn es entweder allein steht oder das zugehörige else ebenfalls nur eine Anweisung beinhaltet. Aber ein Gemisch in der gleichen Abfrage ( if - else if - else ) finde ich nicht schön.



  • Nexus schrieb:

    Aber ein Gemisch in der gleichen Abfrage ( if - else if - else ) finde ich nicht schön.

    Da hast du vollkommen Recht, weniger schön geht nicht! Ich persönlich mag überhaupt keine if's ohne geschweifte Klammern, weder optisch noch auf die Übersicht bezogen. Aber wenn's alleine da steht, dann ist das schon legitim (nur eben nicht meinem Geschmack entsprechend). Ganz schlimm sind aber die ein-Zeilen-if's der Marke "if(!bTrue) return;". Das geht nun wirklich nicht (und unser Code ist voll davon... 😉 ).



  • Danke erstmal für Eure Antworten. (Das mit den Klammern diskutiere ich jetzt mal nicht aus, denn darum ging's mir nicht.)
    Ich hätte aber noch eine Frage: Wenn ich Stil 3 benutze und dort lokale Variablen habe, legt er die dann nicht an, obwohl er sie unter Umständen gar nicht braucht?

    bool Funktion()
    {
    	if (!Bedingung)
    		return false;
    
    	int zahl;
    	string text;
    
    	TuWas;
    	TuMehr;
    
    	return true;
    }
    

    Mir ist nämlich aufgefallen: Auch wenn ich die Variablen mitten in der Funktion deklariere, kennt sie der Debugger bereits am Anfang. Variablen dagegen, die sich innerhalb einer if-Anweisung befinden, kennt er erst, sobald er dort hineingeht. Kann es also sein, daß das Programm sämtliche Variablen, die im aktuellen Gültigkeitsbereich liegen, schonmal anlegt, auch wenn sie im Quelltext erst weiter unten deklariert werden? In dem Fall wäre ja einer der beiden ersten Stile wohl doch besser, da die Variablen da ja erst angelegt werden, wenn er in den if bzw. else-Zweig geht und somit keine überflüssigen Schritte ausführt.


Anmelden zum Antworten