Defaultwerte überschreiben



  • Dravere schrieb:

    Und dann noch dazu was:

    Nexus schrieb:

    Eine Überladung hat auch Nachteile: Z.B. ist bei einem Defaultparameter der Zusammenhang von Aufrufen mit 0 und 1 Argumenten unmittelbar aus der Deklaration ersichtlich, eine Überladung erfordert hingegen ein Lesen der Dokumentation.

    Also ich erwarte von einem guten Programmierer, dass er die Dokumentation vor dem Verwenden der Schnittstelle liest 😉

    Grüssli

    Ich denke diese Ansicht ist zu idealistisch und nicht real. Auch wenn jemand eine Dokumentation liest kann er sich kaum alles im Kopf behalten.
    Im übrigen bevorzuge ich (wie wahrscheinlich jeder) eine intuitiv korrekt benutzbare Schnittstelle.

    In diesem speziellen Fall ist es ja auch noch so, dass man sicher ist, dass die Funktion mit dem Defaultparameter das gleiche macht, wie wenn ich selbst den Wert übergeben würde. Was bei einer separaten Funktion nicht der Fall ist. Kann, denke ich wichtig sein.



  • Dravere schrieb:

    Wollen wir uns vielleicht darauf einigen:
    - Virtuelle Methoden sollen private sein, gehören nicht zur öffentlichen Schnittstelle und haben keine Defaultwerte. Somit kann man sich nicht auf Defaultwerte verlassen.
    - Nicht-virtuelle Methoden rufen die virtuellen Methoden auf. Durch Überladung können Standardverhalten ausgelöst werden, diese sind allerdings ein Implementierungsdetail und tauchen in der Schnittstelle nicht auf.

    NVI ist zwar ein nettes Idiom, aber gehört meiner Ansicht nach zu jenen Techniken, deren kategorische Durchsetzung in Frage gestellt werden kann. Teilweise ist es nützlich, aber es hat eben auch Nachteile. In vielen Fällen habe ich durch die nicht-virtuelle Schnittstelle beispielsweise keinen Gewinn, muss aber mehr Methoden implementieren, überblicken und konsistent halten. Ausserdem wird mit NVI der qualifizierte Methodenaufruf verhindert.


  • Administrator

    drakon schrieb:

    Ich denke diese Ansicht ist zu idealistisch und nicht real. Auch wenn jemand eine Dokumentation liest kann er sich kaum alles im Kopf behalten.

    Es tut mir leid, aber bevor man eine Bibliothek verwendet, liest man die Dokumentation dazu. Das gehört sich nicht nur, dass ist absolute Pflicht. Und wenn jemand das nicht macht, dann ist er zu 100% selbst Schuld, wenn es danach nicht so läuft, wie es soll.

    drakon schrieb:

    Im übrigen bevorzuge ich (wie wahrscheinlich jeder) eine intuitiv korrekt benutzbare Schnittstelle.

    Definitiv, heisst aber nicht, dass man dann die Dokumentation nicht lesen sollte.

    drakon schrieb:

    In diesem speziellen Fall ist es ja auch noch so, dass man sicher ist, dass die Funktion mit dem Defaultparameter das gleiche macht, wie wenn ich selbst den Wert übergeben würde. Was bei einer separaten Funktion nicht der Fall ist. Kann, denke ich wichtig sein.

    Nein, wie schon mehrfach im Thread aufgezeigt, kann man sich davon eben nicht einfach so sicher sein, weil es unintuitiv und inkonsistent ist. Nexus hat da eigentlich ein sehr gutes Beispiel gezeigt. Defaultparameter sind versteckt, daher sind sie gefährlich, während wenn du die Parameter selber übergibst, es kein Problem darstellt.

    Bei einer separaten Funktion, hat es dich gar nicht zu kümmern, was tatsächlich wie und wo übergeben wird. Die separate Funktion garantiert eine Funktionalität, was da tatsächlich passiert, kann dem Programmierer völlig egal sein. Bei Defaultparameter muss es immer der Programmierer nochmals prüfen, ob es wirklich das ist, was er will.

    Nexus schrieb:

    NVI ist zwar ein nettes Idiom, aber gehört meiner Ansicht nach zu jenen Techniken, deren kategorische Durchsetzung in Frage gestellt werden kann. Teilweise ist es nützlich, aber es hat eben auch Nachteile. In vielen Fällen habe ich durch die nicht-virtuelle Schnittstelle beispielsweise keinen Gewinn, muss aber mehr Methoden implementieren, überblicken und konsistent halten. Ausserdem wird mit NVI der qualifizierte Methodenaufruf verhindert.

    Was ist NVI? Non-Virtual-Interface?
    Welche Techniken sollte man nicht in Frage stellen, welche kategorisch durchgesetzt werden? 🙂
    Der Mehrgewinn ist meistens minimal bei der ersten Umsetzung, kann sich aber bei Veränderungen plötzlich positiv auswirken. Schreibfaulheit als Begründung vorzuweisen, halte ich für eine ziemlich schwache Begründung. Auch dass es sehr viel mehr Funktionen werden, halte ich für falsch. Meistens hat man in Klassen nur sehr wenige virtuelle Funktionen, zumindest ist das in meinen Hierarchien oft der Fall. Und bei den wenigen Klassen, wo man doch mal mehr hat, kommt man dann vielleicht in die Ausnahmeregelung rein 😉

    Der qualifizierte Aufruf von virtuellen Methoden ausserhalb der Klasse und nicht in der Hierarchie, halte ich sowieso für einen Indikator eines schlechten Designs.

    Grüssli



  • Dravere schrieb:

    drakon schrieb:

    Im übrigen bevorzuge ich (wie wahrscheinlich jeder) eine intuitiv korrekt benutzbare Schnittstelle.

    Definitiv, heisst aber nicht, dass man dann die Dokumentation nicht lesen sollte.

    Erzähl mir nicht, dass du jede einzelne Funktion nachlesen gehst. Ein SetPosition() setzt die Position, ein operator+ addiert. Sehr oft ist der Kontext klar. Und ich wollte nur darauf hinweisen, dass eine Überladung eine Unsicherheit mit sich bringt und den Benutzer dazu zwingt, eine zusätzliche Funktion nachzuschlagen. Wenn man bei einer Defaultparameter-Funktion die voll qualifizierte Version bereits kennt und sieht, dass es einen Defaultparameter gibt, hat man keinen Aufwand. Ist zwar nur ein kleiner Vorteil, aber ist einer. Ganz besonders, wenn die Überladung eine unintuitive Überraschung bereithält.

    Dravere schrieb:

    Defaultparameter sind versteckt, daher sind sie gefährlich, während wenn du die Parameter selber übergibst, es kein Problem darstellt.

    Überladung hat diesbezüglich das genau gleiche Problem. Wenn du kein explizites Argument übergibst, wird ein verstecktes übergeben. Dessen Implementierung kann sich genauso ändern. Klar kann man mit Überladung eine unabhängige Funktionalität verbinden, aber diese Distanz besteht bei Defaultparametern nicht.

    Dravere schrieb:

    Was ist NVI? Non-Virtual-Interface?

    Ja.

    Dravere schrieb:

    Schreibfaulheit als Begründung vorzuweisen, halte ich für eine ziemlich schwache Begründung.

    Du hast die Angewohnheit, vorschnell Schreibfaulheit in ein Argument hineinzuinterpretieren. Doch lies meinen Satz nochmals, da steht lediglich "[ich] muss aber mehr Methoden implementieren, überblicken und konsistent halten".

    Dravere schrieb:

    Auch dass es sehr viel mehr Funktionen werden, halte ich für falsch.

    Ich sagte ja auch nur "mehr" und nicht "sehr viel mehr". Viel wichtiger ist jedoch, dass der genannte Mehraufwand mir oft keinen Vorteil bringt.

    Dravere schrieb:

    Der qualifizierte Aufruf von virtuellen Methoden ausserhalb der Klasse und nicht in der Hierarchie, halte ich sowieso für einen Indikator eines schlechten Designs.

    Ja, aber du verunmöglichst mit privaten virtuellen Funktionen auch Aufrufe von Basisklassenmethoden in einer überschriebenen Methode. Und sag jetzt nicht, das sei auch schlechtes Design.


  • Administrator

    Nexus schrieb:

    Erzähl mir nicht, dass du jede einzelne Funktion nachlesen gehst.

    Ich kann dir definitiv erzählen, dass ich vor jeder Verwendung einer Klasse, diese zuerst mal in der Dokumentation anschaue. Was bietet sie an, wie ist sie aufgebaut, wie verwendet man sie.

    Nexus schrieb:

    Wenn man bei einer Defaultparameter-Funktion die voll qualifizierte Version bereits kennt und sieht, dass es einen Defaultparameter gibt, hat man keinen Aufwand. Ist zwar nur ein kleiner Vorteil, aber ist einer.

    Halte ich nicht mal für einen Vorteil, sondern für eine zusätzliche Gefahrenquelle, auf welche der Benutzer der Bibliothek achten muss.

    Nexus schrieb:

    Dravere schrieb:

    Defaultparameter sind versteckt, daher sind sie gefährlich, während wenn du die Parameter selber übergibst, es kein Problem darstellt.

    Überladung hat diesbezüglich das genau gleiche Problem. Wenn du kein explizites Argument übergibst, wird ein verstecktes übergeben. Dessen Implementierung kann sich genauso ändern. Klar kann man mit Überladung eine unabhängige Funktionalität verbinden, aber diese Distanz besteht bei Defaultparametern nicht.

    Ich möchte nochmals darauf hinweisen, worum es mir genau ging. Es geht darum, bei wem schlussendlich die Verwantwortung liegt. Bei Defaultparameter liegt die Verantwortung beim Programmierer, welcher die Bibliothek verwendet. Er muss darauf aufpassen, dass etwas korrekt läuft, was vor ihm versteckt wird! Wenn es eine Überladung ist, dann liegt die Verantwortung klar beim Autoren der Bibliothek.

    Nexus schrieb:

    Du hast die Angewohnheit, vorschnell Schreibfaulheit in ein Argument hineinzuinterpretieren.

    Hab ich das wirklich? Ist mir noch nicht aufgefallen 🙂

    Nexus schrieb:

    Doch lies meinen Satz nochmals, da steht lediglich "[ich] muss aber mehr Methoden implementieren, überblicken und konsistent halten".

    Mehr Methoden implementieren -> du willst nicht mehr schreiben ... stimmt doch?
    Mehr Methoden überblicken und konsistent halten -> Darauf bin ich danach eingegangen, wo ich gleich auch nochmals eingehen werde 🙂

    Nexus schrieb:

    Ich sagte ja auch nur "mehr" und nicht "sehr viel mehr".

    Könntest du dieses "mehr" mal ein wenig genauer spezifizieren, denn ich halte dies für sehr wichtig bei deiner Argumentation. Wenn du davon sprichst, dass du mehr Methoden überblicken und konsistent halten musst, und dies als Nachteil angibst, dann klingt das für mich, als wenn du 30 zusätzliche Methoden nun hättest. Nach meiner Erfahrung sind es aber vielleicht 1 bis 5. Und bei so wenig zusätzlichen Methoden, kann ich dein Argument einfach nicht gelten lassen. Das ist so mikrig wenig, dass es gar nicht ins Gewicht fällt.

    Nexus schrieb:

    Viel wichtiger ist jedoch, dass der genannte Mehraufwand mir oft keinen Vorteil bringt.

    Vieles bringt einem nicht immer sofort oder überhaupt jemals einen Vorteil. Es geht hier allerdings gerade nicht um einen sofortigen Vorteil. Der Vorteil kann sich erst viele Jahre später äussern. Wir befolgen doch ständig Richtlinien, welche aktuell eher mühsam sind, später aber sehr nützlich sein können. Wer dokumentiert zum Beispiel seinen Quellcode schon gerne? Also ich definitiv nicht und es hat auch gar keinen Vorteil für mich aktuell. Wenn ich später die Sache dann nochmals lese, bin ich dann womöglich froh drum.

    Nexus schrieb:

    Ja, aber du verunmöglichst mit privaten virtuellen Funktionen auch Aufrufe von Basisklassenmethoden in einer überschriebenen Methode. Und sag jetzt nicht, das sei auch schlechtes Design.

    Wenn wir ganz streng sind und uns in diesem NVI Idiom bewegen, doch, dann wäre es schlechtes Design. Denn du musst dann gar nie die Basisfunktionalität aufrufen, denn die Basisklasse hat diese bereits ausgeführt und erst danach an die virtuelle Funktion weitergeleitet. Am Ende führt die Basisklasse dann womöglich wieder selber Code aus. Du musst somit gar nie die Basismethode aufrufen, denn diese hat gar keinen Code.

    Allerdings muss man Richtlinien auch nicht als Dogmen auffassen. Man kann auch mal die Funktion protected machen 🙂

    Grüssli



  • Dravere schrieb:

    Ich kann dir definitiv erzählen, dass ich vor jeder Verwendung einer Klasse, diese zuerst mal in der Dokumentation anschaue. Was bietet sie an, wie ist sie aufgebaut, wie verwendet man sie.

    Sich Überblick verschaffen ist ja auch ganz okay. Aber sich jede offensichtliche Funktionsdokumentation genau durchzulesen, nur um zu merken, dass sie gleich viel wie der Funktionsname aussagt... Naja. 😉

    Dravere schrieb:

    Ich möchte nochmals darauf hinweisen, worum es mir genau ging. Es geht darum, bei wem schlussendlich die Verwantwortung liegt. Bei Defaultparameter liegt die Verantwortung beim Programmierer, welcher die Bibliothek verwendet. Er muss darauf aufpassen, dass etwas korrekt läuft, was vor ihm versteckt wird! Wenn es eine Überladung ist, dann liegt die Verantwortung klar beim Autoren der Bibliothek.

    Beim Defaultparameter wird insofern nichts versteckt, als dem Aufrufer klar ist, wie ein Aufruf aussieht – nämlich genau gleich, wie wenn er den Wert hinter dem = selbst übergeben würde. Bei der Überladung hingegen muss man sich vergewissern. Besonders die Bibliothek einige Funktionen enthält, bei denen man ohne das letzte Argument ein Default-Verhalten kriegt, aber dann plötzlich eine Ausnahme vorkommt, ist sowas heikel. Ich will damit nur sagen, dass Überladung auch nicht nur Vorteile hat. Aber natürlich hat sie welche.

    Dravere schrieb:

    Mehr Methoden implementieren -> du willst nicht mehr schreiben ... stimmt doch?

    Mit "implementieren" meinte ich weniger den Aufwand (denn dieser ist hier wirklich recht klein) als das anschliessende Vorhandensein der Methoden. Sorry, war vielleicht wirklich etwas missverständlich.

    Dravere schrieb:

    Könntest du dieses "mehr" mal ein wenig genauer spezifizieren, denn ich halte dies für sehr wichtig bei deiner Argumentation. [...] Nach meiner Erfahrung sind es aber vielleicht 1 bis 5.

    Ich habe auch etwa in dieser Grössenordnung gedacht, wobei ich mich nicht auf 5 als obere Grenze beschränken würde. Aber fünf unnötige Methoden sind zuviele (ich spreche immer noch vom Fall, dass ich keinen langfristigen Vorteil durch die Indirektion sehe; nicht dass du mich falsch verstehst).

    Dravere schrieb:

    Vieles bringt einem nicht immer sofort oder überhaupt jemals einen Vorteil. Es geht hier allerdings gerade nicht um einen sofortigen Vorteil. Der Vorteil kann sich erst viele Jahre später äussern.

    Voraussicht ist gut, aber man kann nicht immer so denken. Jede Implementierung und jedes Design läuft Gefahr, mit der Zeit Probleme zu verursachen. NVI einzusetzen kann sich später auch als Fehler erweisen, z.B. wenn man plötzlich Basisklassenmethoden aufrufen möchte. Oder wenn die ganze Zeit eine unnötige Komplexität vorhanden war, deren Vorteil man nicht ausspielen konnte oder die sich sogar negativ auswirkte. Um andere Beispiele zu nennen: Du kannst nicht jede Klasse und Funktion als Template schreiben, um später eine allfällige Generizität zu ermöglichen. Oder jede Klasse in drei kleinere aufteilen, weil es möglicherweise einmal erwünscht sein kann, dass nur ein Teil der Funktionalität benötigt wird. Oder für alle verwendeten Typen zuerst mit typedef Aliase einführen, um so abstrakt und frei wie möglich zu bleiben. Stichwort Overenginnering.

    Es müssen Designentscheidungen getroffen werden, die sich später vielleicht als falsch und ungeeignet herausstellen. Ich versuche, NVI wo sinnvoll einzusetzen, aber ebenso, es an unnötigen Stellen zu vermeiden.

    Du hast es bestimmt auch schon erlebt, dass du etwas recht Komplexes implementiert hast – vielleicht mit tiefer Metaprogrammierung und am Rande des in C++ Möglichen – und danach stolz auf das Resultat warst. Und später kommt die Einsicht, dass man es mit dem naheliegenden, einfachen Ansatz mindestens gleich gut hätte vollbringen können. Lustig finde ich dieses Beispiel (Folie 15-18, übrigens sehr interessante Präsentation!).

    Dravere schrieb:

    Wir befolgen doch ständig Richtlinien, welche aktuell eher mühsam sind, später aber sehr nützlich sein können. Wer dokumentiert zum Beispiel seinen Quellcode schon gerne?

    Je nach Code, an dem ich gerade programmiere, kann es eine gute Abwechslung sein. 😉

    Dravere schrieb:

    Wenn wir ganz streng sind und uns in diesem NVI Idiom bewegen, doch, dann wäre es schlechtes Design. Denn du musst dann gar nie die Basisfunktionalität aufrufen, denn die Basisklasse hat diese bereits ausgeführt und erst danach an die virtuelle Funktion weitergeleitet.

    Mit einer Zwischenklasse ist es aber nicht möglich. Die oberste Basisklasse kann nur die Funktion der am meisten abgeleiteten Klasse aufrufen, und umgekehrt.


  • Administrator

    Nexus schrieb:

    Sich Überblick verschaffen ist ja auch ganz okay. Aber sich jede offensichtliche Funktionsdokumentation genau durchzulesen, nur um zu merken, dass sie gleich viel wie der Funktionsname aussagt... Naja. 😉

    Ich habe schon erlebt, dass zum Beispiel ein SetPosition nicht nur die Position gesetzt hat, sondern diese gleich noch übers Netzwerk geschickt hat, obwohl es eigentlich eine weitere Methode gab, welche für das Synchronisieren zuständig war. Stand wunderbar in der Dokumentation, empfand ich allerdings als völlig unlogisch und wäre ich nie darauf gekommen, hätte ich die Dokumentation nicht gelesen. Die grössten Programmierfehler entstehen genau daraus: Der Programmierer nimmt an, dass es so geht, wie er denkt. Auch bei den kleinsten und unscheinbarsten Methoden kann dies plötzlich nicht der Fall sein. Und das Lesen von Getter und Setter Dokumentationen ist wirklich nichts sehr aufwendiges, vielleicht ein wenig öde (:D), aber dafür hat man danach Gewissheit ... sofern die Dokumentation korrekt ist 😉

    Nexus schrieb:

    Beim Defaultparameter wird insofern nichts versteckt, als dem Aufrufer klar ist, wie ein Aufruf aussieht – nämlich genau gleich, wie wenn er den Wert hinter dem = selbst übergeben würde.

    Er sieht diesen Wert aber nicht, weil er sich irgendwo in einem Header befindet, in der tief verschachtelten Hierarchie. Es kommt von der Sichtbarkeit fast genau gleich wie bei einer überladenen Methode, welche dies völlig versteckt. Ich stimme dir absolut zu, dass die Überladung nicht nur Vorteile birgt, aber sie ist meiner Meinung nach vorteilhafter als Defaultparameter, vor allem eben wegen der Zuständigkeit.

    Nexus schrieb:

    Aber fünf unnötige Methoden sind zuviele (ich spreche immer noch vom Fall, dass ich keinen langfristigen Vorteil durch die Indirektion sehe; nicht dass du mich falsch verstehst).

    Das in den Klammern verstehe ich jetzt gar nicht. Oder ich habe eine Vermutung, ich komme darauf aber weiter unten zurück.

    Nexus schrieb:

    ... [voraussicht] ...

    Versteh mich bitte nicht falsch, ich will das nicht als Dogma einführen und alles ausschliesslich so verwenden. Ich stimme dir zu, dass es sicher Ausnahmen gibt, wo man es besser nicht tun sollte. Ich finde es nur ein sehr interessantes Design, welches man ruhig öfters einsetzen sollte. (übrigens, wir entfernen uns langsam immer wie mehr vom Thread-Thema :p)

    Nexus schrieb:

    Mit einer Zwischenklasse ist es aber nicht möglich. Die oberste Basisklasse kann nur die Funktion der am meisten abgeleiteten Klasse aufrufen, und umgekehrt.

    So, hier kehre ich nochmals auf deine Klammeraussage von oben zurück. Hier scheint es mir, dass du das Idiom nicht ganz verstanden hast, denn es ist eigentlich gar nie nötig, auf eine virtuelle Methode einer Basisklasse (egal, ob es nur eine Zwischenklasse ist) zuzugreifen. Die Idee ist ja, dass man die virtuelle Funktion auf das wesentliche beschränkt, auf das was wirklich virtuell sein muss. Vielleicht an etwas pseudo C++ Code erklärt, wie dies aussehen kann:

    class Base
    {
    private:
      virtual void execute_strategy(int param) = 0;
      virtual void preload_strategy(strategy const&) = 0;
    
    private:
      void init_execution_context()
      {
        // ...
      }
    
      void save_execution_context()
      {
        // ...
      }
    
    protected:
      void do_basic_things()
      {
        // ...
        // Wird womöglich von init_execution_context verwendet
        // oder wie auch immer, soll aber auch für Klassen in
        // der Vererbungshierarchie zur Verfügung gestellt
        // werden.
      }
    
    public:
      void use_strategy()
      {
        init_execution_context();
    
        execute_stragety(2423);
    
        save_execution_context();
      }
    
      void set_strategy(strategy const& strategy)
      {
        // Können erneut was zuvor ausführen.
        // In dieser Version aber noch nicht nötig.
    
        preload_strategy(strategy);
    
        // In dieser Version auch am Ende noch nichts nötig.
      }
    };
    

    Ist das so klarer? Es ist halt eine sehr strikte Aufgabenauftrennung, welche definitiv nicht immer sinnvoll ist wie so oft bei Richtlinien und Empfehlungen.

    Grüssli



  • Dravere schrieb:

    Ich habe schon erlebt, dass zum Beispiel ein SetPosition nicht nur die Position gesetzt hat, sondern diese gleich noch übers Netzwerk geschickt hat, obwohl es eigentlich eine weitere Methode gab, welche für das Synchronisieren zuständig war. Stand wunderbar in der Dokumentation, empfand ich allerdings als völlig unlogisch und wäre ich nie darauf gekommen, hätte ich die Dokumentation nicht gelesen. Die grössten Programmierfehler entstehen genau daraus: Der Programmierer nimmt an, dass es so geht, wie er denkt.

    Hier sehe ich den Fehler aber mehr beim Bibliotheksentwickler. Bei anti-intuitiven Funktionen darf man sich nicht über Fehlbenutzung wundern. Für irgendwas gibts ja treffende Funktionsnamen und Trennung der Aufgabenbereiche. Bei a-b erwartet auch niemand, dass in Wirklichkeit eine Multiplikation durchgeführt wird und a anschliessend 0 ist. 😉

    Natürlich sollte man lieber einmal zu viel als zu wenig in die Dokumentation schauen, besonders wenn man eine Bibliothek noch nicht gut kennt.

    Dravere schrieb:

    Er sieht diesen Wert aber nicht, weil er sich irgendwo in einem Header befindet, in der tief verschachtelten Hierarchie.

    Er sieht den Wert in seiner modernen IDE oder in der Dokumentation, die er lesen sollte. :p

    Aber im Ernst: Ich habe schon oft die IntelliSense-Informationen benutzt, um mich über eine Schnittstelle zu informieren. Wenn die Dokumentation gleich über der Funktion steht (z.B. bei Doxygen), ist das viel praktischer als eine externe HTML-Datei abzusuchen.

    Dravere schrieb:

    Nexus schrieb:

    Aber fünf unnötige Methoden sind zuviele (ich spreche immer noch vom Fall, dass ich keinen langfristigen Vorteil durch die Indirektion sehe; nicht dass du mich falsch verstehst).

    Das in den Klammern verstehe ich jetzt gar nicht.

    Ich entscheide je nach Fall, ob ich NVI für sinnvoll halte oder nicht. In der Klammer ging es mir ums Nicht Sinnvoll. Diese hatte ich am Schluss noch kurz hinzugefügt in der Hoffnung, meine Ansicht zu verdeutlichen. Aber scheinbar habe ich das Gegenteil erreicht und noch mehr Verwirrung gestiftet. 😃

    Was ich ursprünglich damit sagen wollte: Du solltest "fünf unnötige Methoden sind zuviele" nicht so verstehen, dass ich durch NVI zusätzlich entstandene Methoden generell für unnötig halte.

    Dravere schrieb:

    Hier scheint es mir, dass du das Idiom nicht ganz verstanden hast, denn es ist eigentlich gar nie nötig, auf eine virtuelle Methode einer Basisklasse (egal, ob es nur eine Zwischenklasse ist) zuzugreifen.

    Danke für die Erklärung. Im Normalfall ist es nicht nötig, das stimmt.

    Was ich eigentlich meinte, war Folgendes:

    class Fahrzeug
    {
        public:
            void TankeAuf()
            {
                // erhalte Möglichkeit für Zusatzbehandlung, Prüfungen etc.
                TankeWirklichAuf():
            }
        private:
            virtual void TankeWirklichAuf() = 0;
    };
    
    class Schiff : public Fahrzeug
    {
        private:
            virtual void TankeWirklichAuf()
            {
                FuelleHauptTanks();
                FuelleNebenTanks();
            }
    
            void FuelleHauptTanks();
            void FuelleNebenTanks();
    };
    
    class HyperSchiff : public Schiff
    {
        private:
            virtual void TankeWirklichAuf()
            {
                Schiff::TankeWirklichAuf(); // <- leider nicht möglich
                FuelleHyperTanks();
            }
    
            void FuelleHyperTanks();
    };
    

    Ich denke aber, wir sehen das Ganze doch nicht so unterschiedlich. 🙂



  • Nexus schrieb:

    Dravere schrieb:

    Ich habe schon erlebt, dass zum Beispiel ein SetPosition nicht nur die Position gesetzt hat, sondern diese gleich noch übers Netzwerk geschickt hat, obwohl es eigentlich eine weitere Methode gab, welche für das Synchronisieren zuständig war. Stand wunderbar in der Dokumentation, empfand ich allerdings als völlig unlogisch und wäre ich nie darauf gekommen, hätte ich die Dokumentation nicht gelesen. Die grössten Programmierfehler entstehen genau daraus: Der Programmierer nimmt an, dass es so geht, wie er denkt.

    Hier sehe ich den Fehler aber mehr beim Bibliotheksentwickler. Bei anti-intuitiven Funktionen darf man sich nicht über Fehlbenutzung wundern. Für irgendwas gibts ja treffende Funktionsnamen und Trennung der Aufgabenbereiche. Bei a-b erwartet auch niemand, dass in Wirklichkeit eine Multiplikation durchgeführt wird und a anschliessend 0 ist. 😉

    Naja. In gewissem sind alle beteiligten Schuld. 😉
    Weil es ja explizit in der Dokumentation steht. Ich halte das aber eher für einen Sonderfall, als die Regel. Kommt halt drauf an, auf was für ein Objekt man das aufruft. Von einem string erwarte ich nicht, dass er irgendwelche Sachen ins Netz propagiert, was ich bei einem Socket nicht ausschliessen kann.
    Auf der anderen Seite hätten die Entwickler das schon trennen können. Wir (ausser Dravere) können aber die genauen Umstände gar nicht kennen.. Könnte imo schon auch Sinn machen, dass sich die Klasse da unintuitiv verhält.

    Klar sollte aber schon die Intuition nicht die Dokumentation ersetzen, sondern sie unterstützen.
    Aber z.B habe ich, als ich vor ein paar Monaten mit Java angefangen habe nie wirklich die gesamte Doku zu den Collections gelesen, sondern einfach mehr oder weniger intuitiv benutzt. Wenn ich dann etwas doch nachgelesen habe, dann stand da eigentlich immer sowieso das, was ich angenommen habe. (Elemente einfügen, entfernen, suchen, zugreifen usw. - wüsste nicht, warum ich das lesen hätte sollen)


  • Administrator

    Nexus schrieb:

    Hier sehe ich den Fehler aber mehr beim Bibliotheksentwickler. Bei anti-intuitiven Funktionen darf man sich nicht über Fehlbenutzung wundern.

    Problem ist, dass das intuitive Empfinden oft sehr subjektiv ist. 😉

    Nexus schrieb:

    Was ich eigentlich meinte, war Folgendes:

    Und wieso nicht so:

    class Fahrzeug
    {
    public:
      void TankeAuf()
      {
        // erhalte Möglichkeit für Zusatzbehandlung, Prüfungen etc.
        TankeWirklichAuf():
      }
    private:
      virtual void TankeWirklichAuf() = 0;
    };
    
    class Schiff : public Fahrzeug
    {
    private:
      virtual void TankeWirklichAuf()
      {
        FuelleAlleTanks();
      }
    
    protected:
      void FuelleAlleTanks()
      {
        FuelleHauptTanks();
        FuelleNebenTanks();
      }
    
      void FuelleHauptTanks();
      void FuelleNebenTanks();
    };
    
    class HyperSchiff : public Schiff
    {
    private:
      virtual void TankeWirklichAuf()
      {
        Schiff::FuelleAlleTanks(); // <- geht doch! ;)
    
        FuelleHyperTanks();
      }
    
      void FuelleHyperTanks();
    };
    

    Nexus schrieb:

    Ich denke aber, wir sehen das Ganze doch nicht so unterschiedlich. 🙂

    Glaube ich auch nicht 🙂

    Grüssli



  • Dravere schrieb:

    Und wieso nicht so:

    Genau. 🙂
    Aber eben: Streng genommen ist es ja nicht mehr privat. Ich glaube jedoch, wir sind uns einig, was das betrifft. 😉

    Danke aber, dass du dir solche Mühe für das Ändern der Einrückung gegeben hast. 😃

    Und sorry drakon, dass deine Posts in der Flut untergegangen sind. 🤡
    (Keine Sorge, ich habe sie schon gelesen und stimme ihnen weitgehend zu :)).



  • Nexus schrieb:

    Und sorry drakon, dass deine Posts in der Flut untergegangen sind. 🤡
    (Keine Sorge, ich habe sie schon gelesen und stimme ihnen weitgehend zu :)).

    Schon gut. Ich wollt euch ja nicht unterbrechen. 🤡


  • Administrator

    Nexus schrieb:

    Aber eben: Streng genommen ist es ja nicht mehr privat. Ich glaube jedoch, wir sind uns einig, was das betrifft. 😉

    Jein. Man kann es wirklich private machen, wenn man möchte und muss. 🙂
    Vielleicht nur eine kleine Ergänzung noch, welche mir gestern spät am Abend noch einfiel, wo der Computer aber schon aus war.
    Das praktische ist, wenn die virtuelle Funktion aufgerufen wird, dann weiss man, dass es den Aufruf der letzten Überschreibung ist. Die Methode kann schliesslich nicht anders aufgerufen werden (abgesehen davon, wenn der Programmierer es selber in der eigenen Klasse macht). Du hast somit einen sehr klar definierten Zustand, welchen du ausnützen kannst, wenn du musst.

    Nexus schrieb:

    Danke aber, dass du dir solche Mühe für das Ändern der Einrückung gegeben hast. 😃

    Ist das eigentlich dein Standardeinrückungsstil? Sollen wir über sowas auch noch diskutieren? 😃

    @drakon,
    Oh, du bist ja auch noch hier 🤡

    Grüssli



  • Dravere schrieb:

    Ist das eigentlich dein Standardeinrückungsstil?

    Ja. Ein Tab ist bei mir so gross wie vier Leerzeichen, und Zugriffsspezifizierer fangen eingerückt an.

    Ich habe mir allerdings auch schon überlegt, ob ich die Einrückungsebene sparen sollte. Z.B. bei switch ist es ähnlich:

    switch (var)
    {
        case 1:
            Bla();
            break;
    
        default:
            Bli();
            break;
    }
    

    vs.

    switch (var)
    {
    case 1:
        Bla();
        break;
    
    default:
        Bli();
        break;
    }
    

    Ich finde die erste Variante leicht übersichtlicher, und der Bildschirm ist eigentlich genügend breit. Klassen haben dann für friend s die gleiche Einrückungsstufe wie für public: .

    Du benutzt nur zwei Leerzeichen pro Einrückungsebene?



  • Nexus schrieb:

    Du benutzt nur zwei Leerzeichen pro Einrückungsebene?

    Oh, das ist aber schön. Ich benutze auch gerne zwei. 👍


  • Administrator

    Nexus schrieb:

    Du benutzt nur zwei Leerzeichen pro Einrückungsebene?

    Im Forum ja, weil es einfacher ist und grundsätzlich nicht mehr benötigt. Ein einziges Leerzeichen ist allerdings zu wenig. Ansonsten verwende ich aber oft Tab und lasse ihn durch 4 Leerzeichen ersetzen. Ich muss allerdings gestehen, dass ich schon mit dem Gedanken gespielt habe, überall auf 2 Leerzeichen runter zu gehen. Es reicht grundsätzlich völlig aus, also wieso noch mehr in die Breite gehen? Vielleicht mache ich mal einen Kompromiss und nehme 3 😃

    Grüssli



  • In machen Sprachen benutze ich nur eins. Aber drei? Auf die Idee, eine Nicht-Zweierpotenz zu nehmen, wäre ich nie gekommen.


Anmelden zum Antworten