Anti-Patterns: Was ist eine "Gottklasse" ?



  • Naja, mit Kapselung hat das tatsächlich nichts zutun.

    Das sehe ich nicht so. Wenn die Funktion, die lediglich Funktionen braucht, die öffentlich verfügbar sind, dann heisst das ja nicht, dass man zwangsweise auch nur diese benutzt. Sprich für mich als Programmierer dieser Funktion verschafft das auslagern auch ein gewisses Mass an Sicherheit, dass ich die Invariante nicht störe. Sprich es ist mir gar nicht möglich, wenn die Funktion ausgelagert ist. (Sofern die öffentlichen Funktionen das nicht tun).

    Wenn ich z.B eine Fenster Klasse habe, die triviale getter, welche die X/Y Koordinaten zurückgeben, haben und dann in einer Funktion aus irgendeinem Grund halt anstatt die getX -Funktion zu benutzen einfach direkt auf den Wert zugreife, dann hast du sofort ein Problem, wenn du jetzt die getter auf z.B relative Koordinaten umrechen musst (was dann im getter passiert). Eine Funktion, die von aussen darauf zugreift, benutzt brav die getter und alles funktioniert wunderprächtig. Aber in der Funktion, die halt dann doch die Member benutzt läuft alles schief. Und das ist imo ein unnötiges Risiko.



  • - Gauss(); // Gauss-Algorithmus (Matrix auf Dreiecksform bringen)
    - Decomposite(L,U); // LR-Zerlegung durchführen (A=L*U)
    - ColumnPivotSearch(); // Spaltenpivotsuche
    - TotalPivotSearch(); // Vollständige Pivotsuche

    Das sind Algorithmen und brauchen nicht als Methoden der Klasse implementiert sein.



  • drakon schrieb:

    Naja, mit Kapselung hat das tatsächlich nichts zutun.

    Das sehe ich nicht so. Wenn die Funktion, die lediglich Funktionen braucht, die öffentlich verfügbar sind, dann heisst das ja nicht, dass man zwangsweise auch nur diese benutzt. Sprich für mich als Programmierer dieser Funktion verschafft das auslagern auch ein gewisses Mass an Sicherheit, dass ich die Invariante nicht störe. Sprich es ist mir gar nicht möglich, wenn die Funktion ausgelagert ist. (Sofern die öffentlichen Funktionen das nicht tun).

    Wenn ich z.B eine Fenster Klasse habe, die triviale getter, welche die X/Y Koordinaten zurückgeben, haben und dann in einer Funktion aus irgendeinem Grund halt anstatt die getX -Funktion zu benutzen einfach direkt auf den Wert zugreife, dann hast du sofort ein Problem, wenn du jetzt die getter auf z.B relative Koordinaten umrechen musst (was dann im getter passiert). Eine Funktion, die von aussen darauf zugreift, benutzt brav die getter und alles funktioniert wunderprächtig. Aber in der Funktion, die halt dann doch die Member benutzt läuft alles schief. Und das ist imo ein unnötiges Risiko.

    Und wer garantiert dir, das die ausgelagerte Funktion noch richtig funktioniert, wenn sich plötzlich die Schnittstelle ändert und relative statt absolute koordinaten zurück gibt?

    Dieses ganze Auslagern und überall Wiederverwenden solcher Funktionen führt meiner Meinung nach nur zu "Gottfunktionen". Wenn die Funktionen nicht so trivial wie reload sind und irgendwas geändert werden muss, dann kann es schnell passieren, dass die Funktion für ein paar Klassen nicht mehr das tut was sie soll. Das führt einfach nur zu unzähligen Abhängigkeiten im Code.



  • KasF und drakon! Ja, stimmt. Fällt mir jetzt auch so langsam auf, das die Kapselung besser gewahrt wird. Um so besser, noch ein Argument für die freien Funktionen. 🙂

    @tralala! Wenn sich das Verhalten der Schnittstelle ändert, ist das mit oder ohne freie Funktion ein Problem. Eine Schnittstelle soll genau sagen, was sie tut. getX() und getY() ist natürlich generell undeutlich. Man kann deshalb das Verhalten dieser Funktionen in der API-Doku beschreiben (natürlich darf man die Doku nicht nachträglich ändern...).

    Am besten wäre aber natürlich ein eindeutiger Funktionsname, wie getRelativX() und getRelativY() ! Und wenn die Implementierung auf Einmal absolute Werte liefert, dann ist das ein Fehler. Aber was hat da die freie Funktion damit zu schaffen? Die ist total unschuldig. Denn nicht nur die frei Funktion wäre von diesem Fehler betroffen, sondern JEDER der die getter-Funktionen benutzt.



  • Artchi schrieb:

    Am besten wäre aber natürlich ein eindeutiger Funktionsname, wie getRelativX() und getRelativY() !

    Wobei natürlich auch das nicht eindeutig sein muss. 😉

    Ich denke, man kann die Funktionsnamen soweit so bennen, sofern es im logischen Kontext Sinn macht, oder wenn es unterschiedliche Möglichkeiten gibt, wie eben das Abfragen der absoluten oder relativen Koordinaten. Den Rest muss halt in der Referenz stehen. Sonst müsste man ja so etwas schreiben:

    void getx_relative_and_clamp_to_0_1024 (){...}
    


  • Wenn eine Funktion ala "getx_relative_and_clamp_to_0_1024" wirklich Sinn macht, dann sollte sie auch so heissen. OK, das "relative" sollte man IMO weglassen.



  • Artchi schrieb:

    @tralala! Wenn sich das Verhalten der Schnittstelle ändert, ist das mit oder ohne freie Funktion ein Problem. Eine Schnittstelle soll genau sagen, was sie tut. getX() und getY() ist natürlich generell undeutlich. Man kann deshalb das Verhalten dieser Funktionen in der API-Doku beschreiben (natürlich darf man die Doku nicht nachträglich ändern...).

    Am besten wäre aber natürlich ein eindeutiger Funktionsname, wie getRelativX() und getRelativY() ! Und wenn die Implementierung auf Einmal absolute Werte liefert, dann ist das ein Fehler. Aber was hat da die freie Funktion damit zu schaffen? Die ist total unschuldig. Denn nicht nur die frei Funktion wäre von diesem Fehler betroffen, sondern JEDER der die getter-Funktionen benutzt.

    Ja, seh ich auch so, aber ich versteh nicht, was es dann bringt eine freie Funktion zu machen, statt sie in der Klasse zu lassen. Ich versteh einfach nicht was an drakons Beispiel jetzt besser gekapselt und sicherer sein soll bzw. wieso man die Funktion nicht in der Klasse lassen kann, man kann ja trotzdem die getter verwenden. Ich finde halt einfach das die Gefahr, dass man sich ungewollte abhängigkeiten schafft viel größer ist, vorallem, wenn man die freien Funktionen so schreibt, dass sie auch von anderen Klassen verwendet werden können.



  • Artchi schrieb:

    Auf den ersten Blick sehe ich SwapCols und SwapRows.

    Das ist sinnfrei. Gerade diese beiden Methoden profitieren enorm vom Wissen der Speicherung der Daten in einer Matrix. Außerhalb der Klasse sind die Elemente nur noch über die Indizes ansprechbar, während innerhalb der Klasse man ggf. Zeiger auf Spalten oder Reihen vertauschen kann.



  • tralala schrieb:

    Ja, seh ich auch so, aber ich versteh nicht, was es dann bringt eine freie Funktion zu machen, statt sie in der Klasse zu lassen.

    Lassen wir den Guru etwas dazu sagen:

    http://www.gotw.ca/gotw/084.htm



  • Und was sagt der Guru dazu?

    tralala schrieb:

    Ich finde halt einfach das die Gefahr, dass man sich ungewollte abhängigkeiten schafft viel größer ist, vorallem, wenn man die freien Funktionen so schreibt, dass sie auch von anderen Klassen verwendet werden können.



  • tralala schrieb:

    Und was sagt der Guru dazu?

    tralala schrieb:

    Ich finde halt einfach das die Gefahr, dass man sich ungewollte abhängigkeiten schafft viel größer ist, vorallem, wenn man die freien Funktionen so schreibt, dass sie auch von anderen Klassen verwendet werden können.

    Toller Guru 😉 Aber du warst da glaub ich nicht gemeint...

    Ich weiß nicht welche "ungewollten Abhängigkeiten" du meinst... Sei mal deutlicher!
    Es geht hier auch um den generischen Ansatz.

    template < typename ContainerT >
    void clear( ContainerT &cnt )
    {
        cnt.erase( cnt.begin(), cnt.end() );
    }
    

    Sollte für vector/list/... aber eben auch für basic_string funktionieren.
    Stattdessen gibt es das clear() in den Klassen vector/... und basic_string.

    Und wenn deine allgemeine freie Funktion mal ausnahmsweise für einen bestimmten Typen anders implementiert wird (weil die Schnittstelle anders ist, blame you), dann gibt es eine Spezialisierung, die man auch gleich über den Header der nötigenden Klasse bekommt.

    Ich seh immer noch keine Abhängigkeiten...



  • Naja, das sind hier immer nur so trivial Beispiele mit Funktionen die aus 2 bis 3 Aktionen bestehen. Wer programmiert denn täglich irgendwelche Container mit so klar definiertem verhalten? Normal programmiert man irgendwelche kompliziertere Anwendungslogik und da finde ich es gefährlich alles aus den Klassen raus zu ziehen. Wenn die Anwenungslogik sich ändert oder nen Bug für einen Fall hatte, musst du schauen, ob andere Klassen die gleiche Funktion verwenden und ob sich da das verhalten auch ändern soll oder ob du hier und da was spezialisieren musst usw...


Anmelden zum Antworten