Methoden mit bool-Parametern und "Lesbarkeit" im Kontext



  • Xin schrieb:

    In C nehme ich ein integer und definiere Flags.
    ...
    In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.

    wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde 😕



  • Undertaker schrieb:

    Xin schrieb:

    In C nehme ich ein integer und definiere Flags.
    ...
    In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.

    wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde 😕

    Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.

    Frage ich USE_FULLSCREEN auf ein anderenObjektyp->Flags ab, kommt ein Ergebnis dabei rum, dass aber u.U. gar nicht so gewünscht ist. Gibt es mehrere Klassen, kann man sich schnell mal vertun: Ist BUTTON_USE_BORDER != WINDOW_USE_BORDER und man vertut sich, kann man stundenlang USE_BORDER lesen, ohne zu bemerken, dass ein BUTTON kein WINDOW ist.

    Der Mehraufwand für viele Klassen rentiert sich sobald man den ersten Fehler gesucht hat.
    Noch wichtiger: es gibt die Sicherheit, dass man keinen derartigen Fehler übersehen haben kann.
    Je weniger Fehler ein Programm haben kann, desto weniger muss ich bedenken, um einen Fehler zu finden.



  • Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?

    class MeinEnum
    {
        public:
            static MeinEnum modiEins = new MeinEnum(1);
            static MeinEnum modiZwo = new MeinEnum(2);
            static MeinEnum modiDrei = new MeinEnum(3);
    
        protected:
            MeinEnum(int modi) { this->modi = modi; }
        private:
            int modi;
    }
    
    // ...
    {
        barMethod(MeinEnum::modiEins);
    }
    

    Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool. Man muss dann einfach nur die Methode richtig bennen: setModal() oder setVisible()

    Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?



  • DEvent schrieb:

    Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?

    Nicht wirklich:

    enum T { bla, blup };
    void foo(int mode) {
    }
    
    foo(7);
    


  • Xin schrieb:

    Undertaker schrieb:

    Xin schrieb:

    In C nehme ich ein integer und definiere Flags.
    ...
    In C++ erstelle ich eine Klasse MyClassFlags und leite davon MyClass ab.

    wieso macht du's in C++ so umständlich, wenn die C-version dort auch funktionieren würde 😕

    Die C-Version ist nicht typsicher, auf Typsicherheit lege ich sehr großen Wert.

    ach so, deshalb. 🙂



  • DEvent schrieb:

    Wenn du schon eine Klasse schreibst, wieso nicht die Enum-Klasse von Java?

    Ich programmiere C++. 😉

    DEvent schrieb:

    Naja, eigentlich ist die Regel doch klar: Wenn man 100% sicher ist, das es nur 2 Modis sind, dann reicht ein bool.

    Yupp, aber trotzdem kann ein Flag helfen.

    OpenWindow( "Titel", WindowFlag::FullScreen );
    

    liest sich einfach besser als

    OpenWindow( "Titel", true );
    

    und macht Kommentierung überflüssig.

    OpenWindow( "Titel", /* Fullscreen? */ true );
    

    DEvent schrieb:

    Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?

    enums sind in C++ einfach ints und befinden sich nichtmals in einem eigenen Namensraum. Man könnte genauso gut #defines nehmen.
    In der Beziehung schaue ich leicht neidisch in Richtung C#.



  • OpenWindow (title = "Titel", fullscreen = true);
    

    Ist aber schon noch um einiges cooler 😉

    Xin schrieb:

    DEvent schrieb:

    Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?

    [...]Man könnte genauso gut #defines nehmen.

    Das stimmt so nicht.

    enum A { a, b };
    enum B { x, y };
    
    void foo (A x) {}
    
    int main ()
    {
        foo (0);
        foo (x);
    }
    

    liefert folgende wunderschöne Compilerfehler:

    test.cpp: In function 'int main()':
    test.cpp:9: error: invalid conversion from 'int' to 'A'
    test.cpp:9: error:   initializing argument 1 of 'void foo(A)'
    test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'
    

    Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.



  • .filmor schrieb:

    Xin schrieb:

    [...]Man könnte genauso gut #defines nehmen.

    Das stimmt so nicht.

    enum A { a, b };
    enum B { x, y };
    
    void foo (A x) {}
    
    int main ()
    {
        foo (0);
        foo (x);
    }
    

    liefert folgende wunderschöne Compilerfehler:

    test.cpp: In function 'int main()':
    test.cpp:9: error: invalid conversion from 'int' to 'A'
    test.cpp:9: error:   initializing argument 1 of 'void foo(A)'
    test.cpp:10: error: cannot convert 'B' to 'A' for argument '1' to 'void foo(A)'
    

    Das enum's nicht besonders toll sind will ich nicht bestreiten, aber so mies muss man sie auch nicht machen.

    Wow, das muss ich morgen nochmal ausprobieren. Als ich das letzte Mal enum nutzte, wurden die einfach als int verbraten. Darum habe ich sie eigentlich seitdem ignoriert.



  • Xin schrieb:

    Ich programmiere C++. 😉

    dachte ich haette ein C++ Beispiel gebracht? 😕



  • @Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik 😉

    @Shade:

    Shade Of Mine schrieb:

    DEvent schrieb:

    Btw, wieso nicht einfach ein enum nehmen? Die sind doch auch typsicher, oder?

    Nicht wirklich:

    enum T { bla, blup };
    void foo(int mode) {
    }
    
    foo(7);
    

    Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf void foo(T mode) , wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).



  • CStoll schrieb:

    Ja, wenn die Funktion einen int erwartet, kann sie natürlich einen beliebigen int-Wert bekommen. Wenn du die Definition änderst auf void foo(T mode) , wird sich dein C++ Compiler zu recht beschweren, daß er eine explizite Typumwandlung benötigt (wenn es um C geht, hast du allerdings recht).

    enum T { bla, blup };
    void foo(T mode) {
    }
    
    foo(bla | blup); //error
    

    man ist auf int festgelegt wenn man enums sinnvoll verwenden will. in ganz wenig ausnahmefällen kann man ein foo(T mode) machen, aber in den meisten Fällen braucht man int - denn wozu flags wenn ich sie nicht kombinieren kann?



  • Also eine Lösung, die das Problem löst, wird wohl schwieriger sein. Entweder du hast eine implizite Umwandlung von int nach Flag (wie bei C enum's), dann kann der Compiler aber nicht sicherstellen, daß die übergebenen int's legal sind - oder du hast keine solche Umwandlung (wie bei C++ enum's), dann funktionieren diese Flag-Kombinationen üblicherweise nicht. Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.

    *grübelt* enum's sind doch eigenständige Typen - also wäre es auch erlaubt, die Bit-Operatoren für einen eigenen enum zu überladen:

    enum Flag {...};
    Flag operator|(Flag r, Flag l)
    { return (Flag)((int)r|(int)l); }
    ...
    


  • Jetzt noch den einen Slash durch ein Klammer auf ersetzt und es würde tatsächlich gehen, ja.



  • CStoll schrieb:

    @Xin: Die Verwirrung um verschiedene USE_BORDER's kann dir mit deiner Flag-Klasse genauso passieren wie mit Bit-Arithmetik 😉

    C++ kann die Typen WindowFlags und ButtonFlags problemlos unterscheiden.
    Und da WindowFlags keinen Operator & anbietet, der auf ButtonFlags reagiert, wird der Compiler meckern.

    CStoll schrieb:

    Am besten ist da immer noch die Flag-Klasse von Xin, allerdings als eigenständige Klasse und nicht als Basis.

    Was spricht dagegen die Klasse als Basis zu nutzen. Klappt hier wunderbar.
    Die Klasse benötige ich nur, um WindowFlags von Windows zu unterscheiden und WindowFlags von integers, wenn ich Operatoren überlade.



  • Wozu hast du eigentlich

    MyClass : MyClassFlags { }
    

    , Xin?



  • wie wärs mit
    window open title: "title" fullscreen: true.
    ach moment das is ja die verbotene sprache 🤡 🙄



  • DEvent schrieb:

    Wozu hast du eigentlich

    MyClass : MyClassFlags { }
    

    , Xin?

    Wenn ich die Frage richtig verstehe...?

    Ich mache die Ableitung, weil mein Objekt Flags beinhaltet. Statt eine "hat ein" Beziehung zu machen und eine public-Variable Flags einzuführen, mache ich eine "ist ein" Beziehung daraus, um mir Schreibarbeit zu sparen.
    Die Sache ist ungefährlich, weil es sonst nichts gibt, was sich für MyClassFlags interessiert und so muss ich nicht immer betonen, dass ich mit den Flags des Objektes arbeite.

    Ich will ja wissen, ob das Fenster Fullscreen ist ( "ist ein" Fullscreen-Fenster)

    if( MyWindow & WinFlgs::Fullscreen ) ...
    

    und nicht ob die Flags Fullscreen signalisieren. ("hat ein" Fullscreen-Fenster zu sein)

    if( MyWindow->Flags & WinFlgs::Fullscreen ) ...
    

    Ich finde das semantisch logischer und ich muss halt immernoch weniger tippen 😉



  • Und man verwirrt den Anwender des Codes ungemein. Vorallem wenn man nachher noch eine neue Flagvariante einführt, die zB das backbuffering oder so betrifft 😉 dann wird alles sehr sehr chaotisch.

    schreibarbeit darf _nie_ ein grund sein etwas zu machen.



  • Xin schrieb:

    DEvent schrieb:

    Wozu hast du eigentlich

    MyClass : MyClassFlags { }
    

    , Xin?

    Wenn ich die Frage richtig verstehe...?

    Ich mache die Ableitung, weil mein Objekt Flags beinhaltet. Statt eine "hat ein" Beziehung zu machen und eine public-Variable Flags einzuführen, mache ich eine "ist ein" Beziehung daraus, um mir Schreibarbeit zu sparen.
    Die Sache ist ungefährlich, weil es sonst nichts gibt, was sich für MyClassFlags interessiert und so muss ich nicht immer betonen, dass ich mit den Flags des Objektes arbeite.

    Ich will ja wissen, ob das Fenster Fullscreen ist ( "ist ein" Fullscreen-Fenster)

    if( MyWindow & WinFlgs::Fullscreen ) ...
    

    und nicht ob die Flags Fullscreen signalisieren. ("hat ein" Fullscreen-Fenster zu sein)

    if( MyWindow->Flags & WinFlgs::Fullscreen ) ...
    

    Ich finde das semantisch logischer und ich muss halt immernoch weniger tippen 😉

    Sowas funktioniert aber nur mit öffentlicher Vererbung (aber die ist für die Konstallation KlassenFlag - Klasse wenig sinnvoll) oder im Inneren deiner Klassenmethoden (aber dann gibt es keinen Aufwand-Unterschied zwischen 'Flags&WinFlgs::Fullscreen' und '*this&WinFlgs::Fullscreen'). Und du solltest mal darüber nachdenken, was "ist-ein" bzw. "hat-ein" im OOP-Kontext eigentlich bedeutet (Tip: Die Beziehungen beziehen sich auf die beteiligten Klassen).



  • CStoll schrieb:

    Xin schrieb:

    DEvent schrieb:

    Wozu hast du eigentlich

    MyClass : MyClassFlags { }
    

    , Xin?

    Wenn ich die Frage richtig verstehe...?

    Ich mache die Ableitung, weil mein Objekt Flags beinhaltet. Statt eine "hat ein" Beziehung zu machen und eine public-Variable Flags einzuführen, mache ich eine "ist ein" Beziehung daraus, um mir Schreibarbeit zu sparen.
    Die Sache ist ungefährlich, weil es sonst nichts gibt, was sich für MyClassFlags interessiert und so muss ich nicht immer betonen, dass ich mit den Flags des Objektes arbeite.

    Ich will ja wissen, ob das Fenster Fullscreen ist ( "ist ein" Fullscreen-Fenster)

    if( MyWindow & WinFlgs::Fullscreen ) ...
    

    und nicht ob die Flags Fullscreen signalisieren. ("hat ein" Fullscreen-Fenster zu sein)

    if( MyWindow->Flags & WinFlgs::Fullscreen ) ...
    

    Ich finde das semantisch logischer und ich muss halt immernoch weniger tippen 😉

    Sowas funktioniert aber nur mit öffentlicher Vererbung

    Von meiner Seite wurde nie etwas anderes behauptet:

    Xin schrieb:

    class MyClass : public MyClassFlags {};
    

    Warum richtest Du diesen Comment dann an mich?

    CStoll schrieb:

    (aber die ist für die Konstallation KlassenFlag - Klasse wenig sinnvoll)

    Das halte ich sogar für sehr sinnvoll. Von Entwickler wird natürlich hier ein Perspektivenwechsel verlangt. Und genau das ist doch, was den klassenorientierten Ansatz vom strukturierten unterscheidet. Das sollte einem C++-Entwickler also nicht nur zuzumuten sein, sondern er könnte den Aufbau in Hirachien sogar besonders zu schätzen wissen.

    Ein Window ist ein WindowFlags. Die Flags beschreiben nicht das Fenster, sondern das Fenster ist eine Interpretation der Flags. Die WindowFlags werden erweitert, so dass ein viereckiger Kasten (genannt Window) entsteht, der sich an den Flags orientiert.

    CStoll schrieb:

    oder im Inneren deiner Klassenmethoden (aber dann gibt es keinen Aufwand-Unterschied zwischen 'Flags&WinFlgs::Fullscreen' und '*this&WinFlgs::Fullscreen'). Und du solltest mal darüber nachdenken, was "ist-ein" bzw. "hat-ein" im OOP-Kontext eigentlich bedeutet (Tip: Die Beziehungen beziehen sich auf die beteiligten Klassen).

    Darum heißt es ja auch Objektorientiert, weil es sich auf die Klassen bezieht... jaja, ich habe schon viel in diesem Forum gelernt.

    Auch hier biete ich eine Möglichkeit an. Darum heißt es nicht "solltest Du mal darüber nachdenken", sondern "Du hast darüber nachgedacht und das Gesagte ist dabei rausgekommen".
    lolz erfragte Möglichkeiten und wenn Dir funktionierende Antworten darauf nicht gefallen, darfst Du gerne mal darüber nachdenken, sie nicht zu benutzen. Ich freue mich auch, wenn Du Deine qualifizierte Kritik kundtust. Ob Du die mit einem "darüber solltest Du mal nachdenken" mitteilen solltest, halte ich in diesem Fall für zweifelhaft, weil der Fall exakt das beschreibt, was er Deiner Kritik nach nicht beschreibt. Die Bedeutung der Hirachie muss man natürlich sehen und wenn sie Dir dann nicht gefällt, ist das vollkommen in Ordnung. Das wäre eine Geschmacksfrage, keine Frage fachlicher Kompetenz, über die man "nachdenken sollte".
    Daher sage ich Dir, dass Du einen freundlichen gemeinten Rat nicht nur geben kannst, Du darfst ihn auch selbst beherzigen.
    Und Deinen freundlichen Seitenhieb mit dem "Tip" darfst Du Dir entsprechend schenken.

    Deine Kritik erfordert mehr Operatoren im laufenden Sourcetext, die zwar der Compiler so oder so ausführen muss, aber der Entwickler überall im Sourcecode schreiben und - viel schlimmer - immer wieder lesen muss. Das macht den Sourcecode unnötig schwieriger zu lesen. Das scheitert sicherlich nicht an einer Klasse, aber solche Mängel kann man ja an beliebig vielen Klassen begehen, was das Verständnis eines eigentlich trivialen, selbstdokumentierenden Sourcecodes schnell in den Bereich "hebt", der vom Leser nicht mehr als trivial verstanden wird.
    Viele Entwickler überarbeiten nicht triviale Sourcecodes mal eben schnell und bauen so Fehler ein, die man dann wieder in nicht trivialen Sourcecodes suchen muss.

    Das bringt mich wieder zum gleichen Ergebnis wie oben: "Das halte ich sogar für sehr sinnvoll."
    Und ich weiß sogar warum: Ich habe darüber so gründlich nachgedacht, weil ich diesen Mechanismus in meiner Programmiersprache automatisieren möchte.


Anmelden zum Antworten