Designfrage: Attribut VOR Aufruf checken?



  • Abend,

    kleine Designfrage: Wenn eine Klasse ein Attribut mIsVisible besitzt und gewisse Methoden nur durchlaufen sollen, wenn mIsVisible = true ist, sollte man das Attribut dann VOR Aufruf checkern oder IN der Methode?

    Sprich:
    A) VOR Aufruf checken:

    MyClass* m = ...
    if(m->isVisible())
       m->method();  // MyClass::method() prüft NICHT mehr den Status von MyClass::mIsvisible ab
    

    oder
    😎 In der Methode checken:

    MyClass::method() {
       if(!mIsVisible)
          return;
       ...
    }
    
    MyClass* m = ...
    m->method(); // Aufruf unabhängig vom Status von MyClass::mIsVisible
    

    Was findet ihr besser?



  • kleine Designfrage: Wenn eine Klasse ein Attribut mIsVisible besitzt und gewisse Methoden nur durchlaufen sollen, wenn mIsVisible = true ist, sollte man das Attribut dann VOR Aufruf checkern oder IN der Methode?

    Kommt darauf an. Wenn diese Methoden immer nur dann durchlaufen sollen, wenn mIsVisible == true ist, dann Methode 2, sonst Methode 1.

    Schließlich ist es Blödsinn, wenn man vor jeden Funktionsaufruf von method() eine Abfrage machen muss.

    Edit: Gibt auch Fälle wo man es anders machen sollte. Aber dazu hast du zu wenig Hintergrund/Kontext gegeben.



  • Ich würde kategorisch immer VOR dem Aufruf checken lassen.
    Wenn man eine Methode aufruft erwartet man auch, dass etwas passiert.

    Ausserdem kann es unnötig Performance kosten. Angenommen hast ein solches Objekt und musst eine Funktion 1 Million mal hintereinander aufrufen immer mit einem anderen Parameter. Dann ist es besser, den Check einmal ausserhalb der Schleife zu machen als 1 Million mal innerhalb.

    Vor Fehlern schützen muss man sich natürlich trotzdem. Daher solltest du in jeder solchen Funktion ein assert auf die Eigenschaft haben assert(isVisible()) .

    Wichtig ist vor allem, dass die Methoden nicht gemischt werden. Sonst muss vor jedem Aufruf überlegt werden, ob man noch etwas testen muss oder nicht. So passieren dann Fehler. Also immer das gleiche verwenden.

    Die raren Ausnahmen sind delete und die IO-Streams, heute mit Exceptions wäre das sowieso anders gelöst worden.



  • B ist besser, weil man die Methode nicht falsch aufrufen kann.
    Eventuell lohnt es sich aber, die Sichtbarkeitseigenschaft aus der Klasse herauszuziehen und anders auszudrücken.

    Die "sichtbaren" Objekte in einer gesonderten Liste führen:

    struct Renderable
    {
    	void render();
    };
    
    struct Scene
    {
    	void setVisible(Renderable &thing)
    	{
    		m_visibleThings.insert(&thing);
    	}
    
    	void setInvisible(Renderable &thing)
    	{
    		m_visibleThings.erase(&thing);
    	}
    
    	void render()
    	{
    		for (Renderable * const entry : m_visibleThings)
    		{
    			entry->render();
    		}
    	}
    
    private:
    
    	std::set<Renderable *> m_visibleThings;
    };
    

    Decorator pattern:

    struct Renderable
    {
    	virtual ~Renderable();
    	virtual void render() = 0;
    };
    
    struct Hideable : Renderable
    {
    	explicit Hideable(std::unique_ptr<Renderable> realThing, bool isVisible = true)
    		: m_realThing(std::move(realThing))
    		, m_isVisible(isVisible)
    	{
    	}
    
    	void setVisibility(bool isVisible)
    	{
    		m_isVisible = isVisible;
    	}
    
    	virtual void render() override
    	{
    		if (m_isVisible)
    		{
    			m_realThing->render();
    		}
    	}
    
    private:
    
    	std::unique_ptr<Renderable> m_realThing;
    	bool m_isVisible;
    };
    


  • HintergrundKontext schrieb:

    Ich würde kategorisch immer VOR dem Aufruf checken lassen.
    (...)

    Dann würdest du kategorisch schwer wartbaren und sehr fehleranfälligen Code schreiben.
    Solche Dinge checkt man üblicherweise in der Funktion/Methode.
    Und in Spezialfällen nochmal ausserhalb.
    (z.B. wenn man wirklich viele Objekte hat, von denen die allermeisten invisible sind, kann es Sinn machen den Check in die Schleife rauszuziehen die die Funktion/Methode aufruft)



  • TyRoXx:
    Wobei das ja nur Decorator ist, wenn Renderable dann tatsächlich schon die Render-Funktionalität hat. Aber das hast Du jetzt auch angenommen, richtig?

    hustbaer:
    Bei Massenaufruf würdest Du es zusätzlich von außen aufrufen? Das würde doch nicht helfen, die Prüfung geschieht intern doch sowieso stets noch. Oder meinst Du, Du bietest dann zwei Versionen der Funktion an?

    Ich finde diesen Massenaufruf jedoch eh etwas gekünstelt. In TEs Beispiel kann das so ja nicht geschehen, da die Sichtbarkeit für jedes Objekt anders sein kann und somit geprüft werden muss. Der interessante Fall ist doch, wenn man eine Gruppe gebildet hat, in welcher dieses gemeinsame Prüfattribut vorhanden ist. Dann jedoch kann ein einzelnes Objekt dieses Attribut doch überhaupt nicht kennen und somit kann die Prüfung doch gar nicht intern geschehen.

    Die externe Prüfung sollte sich eben auf Attribute beziehen, die nicht dem Objekt selbst zuzuschreiben und kritisch (im Sinne von: die Funktion darf auf keinen Fall aufgerufen werden, wenn das Attribut z.B. false ist) sind. Wenn es sich um externe Eigenschaften handelt, kann die Prüfung aber sowieso nicht intern geschehen; und dann sollte die Methode aber natürlich trotzdem funktionieren, was sie aber sowieso tut.

    Mir fehlt hier irgendwie das Anwendungsbeispiel, wo eine externe Prüfung der Internas einen Vorteil bieten würde, ich sehe bisher keinen.



  • Dein Text ist für mich irgendwie sehr verwirrend.
    Im Beispiel des OP ist es ganz klar ein "internes" Attribut, überlegungen über "externe" Attribute erübrigen sich daher denke ich.

    Eisflamme schrieb:

    hustbaer:
    Bei Massenaufruf würdest Du es zusätzlich von außen aufrufen? Das würde doch nicht helfen, die Prüfung geschieht intern doch sowieso stets noch. Oder meinst Du, Du bietest dann zwei Versionen der Funktion an?

    Nein, ich würde nur eine Version der Funktion anbieten.
    Wenn gerendert wird, dann tut der Test eines bools nicht wirklich weh, weil das Rendern sowieso viel länger dauert.

    Bleibt also noch ein Fall den man optimieren kann, und zwar dass man viele viele Objekte hat von denen nur ganz wenige gerendert werden.
    Und genau da macht ein zusätzlicher, "externer" Test u.U. Sinn. Und zwar dann, wenn die Render-Funktion nicht inline erweitert wird, der Test aber schon.
    Dann spart man sich nämlich die vielen Funktionsaufrufe für die vielen Objekte die eh nicht gerendert werden sollen.

    Ich finde diesen Massenaufruf jedoch eh etwas gekünstelt. In TEs Beispiel kann das so ja nicht geschehen, da die Sichtbarkeit für jedes Objekt anders sein kann und somit geprüft werden muss.

    Ja, darauf beziehe ich mich ja auch.
    Ich rede ja nicht von if - for - render sondern von for - if - render .

    Der interessante Fall ist doch, wenn man eine Gruppe gebildet hat, in welcher dieses gemeinsame Prüfattribut vorhanden ist. Dann jedoch kann ein einzelnes Objekt dieses Attribut doch überhaupt nicht kennen und somit kann die Prüfung doch gar nicht intern geschehen.

    Wieso sollte das der interessante Fall sein? Normalerweise besitzt das Objekt sein eigenes "visible" Attribut, den von dir beschriebenen Fall kann es dann gar nicht geben.

    Mir fehlt hier irgendwie das Anwendungsbeispiel, wo eine externe Prüfung der Internas einen Vorteil bieten würde, ich sehe bisher keinen.

    Siehe oben. Ich meine auch nicht dass man das öfter so machen sollte. Ich wollte damit nur sagen: es kann Fälle geben wo das Sinn macht - wenn auch nicht viele.



  • RobbenCaptn schrieb:

    kleine Designfrage: Wenn eine Klasse ein Attribut mIsVisible besitzt und gewisse Methoden nur durchlaufen sollen, wenn mIsVisible = true ist, sollte man das Attribut dann VOR Aufruf checkern oder IN der Methode?

    Gegenfrage: Stellt es einen Programmier-/Logikfehler dar, wenn die Methode aufgerufen wird, obwohl isVisible gleich false ist? Dann würde ich eher auf ein assert zurückgreifen.

    Mal ein ganz gruseliges Beispiel: Ein Programmierer schreibt eine Funktion, die einen gültigen Pointer als Argument erwartet. Etwas anderes als ein gültiger Pointer stellte in diesem Fall einen massiven Programmfehler dar.

    Nun fügte dieser Programmierer (wahrscheinlich durch einen Absturz o.ä. motiviert) am Anfang dieser Funktion ein "if (ptr == NULL) return;" (oder äquivalent) hinzu. Klar, der Absturz war vielleicht weg. Damit aber auch jede Chance den Fehler in einem anderen Teil des Quellcodes überhaupt erst zu entdecken. Wird ein NULL-Pointer übergeben (dessen reine Existenz an dieser Stelle auf einen Fehler in einem anderen Code-Bereich hinweist), kehrte die Funktion ohne jedes Mucken (aber auch ohne irgendeine Arbeit zu verrichten) einfach zurück. Du kannst dir vorstellen, dass das Finden von Fehlern in derart geschriebenem Code keine schöne Angelegenheit ist.

    Mit defensiver Programmierung hat das auch überhaupt nichts zu tun. Ein klar illegaler Parameter sollte niemals dazu führen, dass die Funktion still und leise einfach gar nichts macht. So versteckt man nur Bugs.

    Sowas willst du aber nicht machen, oder?


Anmelden zum Antworten