frage zu programmierstil (am c++ beispiel)



  • hallo allerseits,

    habe leider zu folgendem thema nicht soviel bei der suche im forum gefunden aber hoffe zumindest ein kommentar von erfahrenen leuten zu bekommen:

    class A
    {
       public:
         A() {myVal=0;mySpeciaVal=0;}
         ~A(){}
    
         int GetVal()
         {
            if( IrgendWasUeberpruefen() )
            {
              myVal = mySpecialVal;
            }
            return myVal;
         }
    
         void SetVal(int newVal)
         {
           if( IrgendWasUeberpruefen() )
             mySpecialVal = newVal;
           else
             myVal = newVal;
         }
    
         bool IrgendWasUeberprufen()
         {
            bool state = false;
            // irgend eine gescheite logik hier und state entsprechend setzen ..
            return state;
         }
         int myVal;
         int specialVal;
    };
    

    Es geht darum, dass obige GetVal Methode offensichtlich seiteneffekte hat.
    Ich hatte bisher immer die sichtweise dass getter (sofern man sie unbedingt haben muss) schon von vornherein als const-methoden deklariert sein sollten und auch im rumpf keine seiten-effekte haben sollten.

    liege ich eigentlich mit dieser sichtweise voellig falsch ?!?! 😕
    falls nicht, gibt es vielleicht online verfuegbare quellen, die meine sichtweise bestaetigen oder in die gleiche richtung gehen !?

    vielen dank.



  • pepe75 schrieb:

    Ich hatte bisher immer die sichtweise dass getter (sofern man sie unbedingt haben muss) schon von vornherein als const-methoden deklariert sein sollten und auch im rumpf keine seiten-effekte haben sollten.

    In der Regel sehe ich das auch so, es gibt aber durchaus auch Ausnahmen (z.B. bei LazyLoad, bei dem die Daten erst beim Zugriff nachgeladen werden). Wobei ich in diesen Sonderfall die Getter weiterhin mit const angebe, und mit mutable arbeite.



  • noch eine anmerkung:

    die member muesste oben eigentlich mySpecialVal heissen (letzte zeile in klasse).

    so verstehe ich, dass obige GetVal() methode doch "schoener" wie folgt aussehe:

    int GetVal() const
    {
       int val = myVal;
       if( IrgendWasUeberpruefen() )
         val = mySpecialVal;
       return val; 
    }
    

    nur dieses "schoener" wuerde ich gerne fuer einen mitmenschen irgendwoher belegt haben ...

    gibt es vielleicht bekannte (oeffentlich verfuegbare) quellen zu diesem thema ? ich benutze glaube ich nicht die richtigen such-begriffe sodass google hierzu nicht viel liefert.

    Zudem ist natuerlich diese aenderung von GetVal() mit fehlen des seiteneffektes damit verbunden, dass entsprechende logik (zum setzen von myVal) an anderen stellen nachprogrammiert werden muesste. das duerfte klar sein.

    vielen dank.



  • pepe75 schrieb:

    so verstehe ich, dass obige GetVal() methode doch "schoener" wie folgt aussehe:

    Die macht aber nicht das gleiche, zudem sehe ich kein Grund für die temporäre Variable (Wenn kein Wert gesetzt werden soll, kann man im if auch gleich ein return machen).

    Wenn ich den ursprünglichen Code richtig verstehe wollte der Orginalautor einen Wert abhängig von einem bestimmten Status setzen und zurückliefern. Dies als solches ist in einer Getter durchaus nicht verkehrt, nicht verständlich für mich ist aber das Überschreiben im Getter.

    pepe75 schrieb:

    gibt es vielleicht bekannte (oeffentlich verfuegbare) quellen zu diesem thema ? ich benutze glaube ich nicht die richtigen such-begriffe sodass google hierzu nicht viel liefert.

    Wenn du google durchsuchst wirst du vermutlich nichts finden, oder gleich 100 verschiedene Meinungen.

    Aber Grundsätzlich glaube ich das Code möglichst verständlich sein sollte. Und wenn man etwas abruft, erwartet man keine Statusänderung. Dazu wirst du schon eher Treffer finden (Vermutlich unter "SOLID"/"KISS" oder Clean Code).



  • Meine Gedanken zu dem Thema...

    1. Der Funktionsname GetVal impliziert einen reinen "getter", d.h.
    1.a. Der Funktionsname impliziert dass GetVal const ist.
    1.b. Der Funktionsname impliziert dass GetVal keine von aussen beobachtbaren Effekte hat.
    Diese üblichen Annahmen nicht zu erfüllen verletzt das Principle Of Least Astonishment (POLA).
    Also entweder GetVal const machen oder umbenennen.

    2. Die "Symmetrie" der Funktionsnamen GetVal und SetVal impliziert dass bei GetVal genau das zurückkommt was man zuletzt mit SetVal reingesteckt hat - die nächste POLA-Verletzung.
    Also entweder die Spezialbehandlung in GetVal entfernen oder eine der beiden Funktionen umbenennen.

    3. Bei GetVal() const + mutable Member sollte man vorsichtig sein, denn const impliziert "thread safe". Seit C++11 ist das sogar (indirekt) als Garantie im Standard verankert. D.h. man muss irgendwie intern synchronisieren (mit Mutexen, Atomics o.Ä.).
    Andernfalls ist es die nächste POLA-Verletzung bzw. seit C++11 sogar UB wenn man die Klasse zusammen mit der Standard-Library verwendet (also in Standard-Container steckt, von Standard-Algorithmen bearbeiten lässt, ...).

    4. Wenn ich die Wahl zwischen der GetVal -Variante habe die die Membervariable modifiziert und der die das nicht tut und statt dessen bei jedem Aufruf checkt ob myVal oder specialVal zurückgegeben werden soll, dann ganz klar letzteres.
    Jegliche Form von "mutable state" macht es schwieriger zu verstehen was eine Klasse macht, wie sie sich in einem bestimmten Programm verhalten wird. Daher ist "vermeidbarer mutable state" böse.
    Und selbst in Situationen wie hier, wo myVal sowieso schon "mutable state" ist (gibt ja immer den Setter), ist es meist deutlich einfacher das Verhalten des Programms nachzuvollziehen, wenn die Variablen an möglicht wenig stellen modifziert werden. Wenn myVal nur im Setter modifiziert wird ist das eine super-einfache Sache, man muss nicht drüber nachdenken ob der Getter schon aufgerufen wurde oder nicht. Es ist immer klar dass myVal einfach genau den Fall hat der zuletzt über SetVal gesetzt wurde. "KISS" hat ja asc schon erwähnt.



  • hustbaer schrieb:

    Meine Gedanken zu dem Thema...
    1. Der Funktionsname GetVal impliziert…

    Das ist ein Foo/Bar::baz-Beispiel. Fertig.

    Ich sage mal, warum der Thread an sich für mich fertig ist.

    Es sind sinvolle Foo-Getter denkbar. Am Ende sogar Setter. Solange man abstrakt bleibt, aber nur dann. 🤡

    Deswegen antworte ich kaum noch auf foobar-Fragen, denn zu 99% will der foobar-Kunde was granatenoffensichtlich Triviales wegvertemplieren, wegvirtualisieren oder sonstwie wegabstrahieren.

    bool IrgendWasUeberprufen()
    

    Danke. Mein erster Gedanke war, daß das IrgendWasUeberprufen aüßerhalb leben sollte und alles wäre paletti.



  • macht ja oft Sinn, dass man bei Getxyz() Methoden auch noch was berechnet.
    Mit der Methode der späten Auswertung und Caching hab ich schon öfters gute Erfahrungen gemacht.

    Also so in der Art - jedenfalls NICHT als const!
    mutable ist natürlich auch eine Lösung, in meinen Augen aber eine unschöne.

    int Foo::Getxxyz()
    {
     if(!m_Cached)
     {
       m_xyz=Berechnexyz();
       m_Cached=true;
     }
    
     return m_xyz;
    }
    


  • fgdfdg schrieb:

    macht ja oft Sinn, dass man bei Getxyz() Methoden auch noch was berechnet.
    Mit der Methode der späten Auswertung und Caching hab ich schon öfters gute Erfahrungen gemacht.

    Also so in der Art - jedenfalls NICHT als const!
    mutable ist natürlich auch eine Lösung, in meinen Augen aber eine unschöne.

    int Foo::Getxxyz()
    {
     if(!m_Cached)
     {
       m_xyz=Berechnexyz();
       m_Cached=true;
     }
     return m_xyz;
    }
    

    Genau für sowas ist mutable da. const bezieht sich nur auf den observable state eines Objekts, wenn sich da Interna ändern müssen die von aussen nicht sichtbar sein und das Objekt darf sie in einer const Methode ändern.

    Edit:
    Typo


Anmelden zum Antworten