Übersichtlichkeit erhalten



  • Je größer meine Programm wird, desto weniger übersichtlich wird der Code.
    So habe ich eine riesige Sprite-Klasse (die Deklaration ging über xyz-Bildschirmseiten) mühseelig zunächst in einer Hierarchie gegliedert, was wiederum relativ viel Zeit verschlungen hat:

    StaticObject -> StaticSprite -> MovingSprite -> AccSprite -> RotAccSprite -> FullSprite

    Dies hat nun zwar den Vorteil, dass *.cpp und *h Dateien übersichtlicher geworden sind, doch dadurch habe ich jetzt im Arbeitsbereich allein im Ordner Sprite 12 Dateien (6 mal *.cpp, 6 mal *.h), was an sich nicht schlimm ist.
    Doch da die Dateien alphabetisch und nicht nach Klassenhierarchie (z.B. erste Basisklasse ganz oben, letzte abgeleitete Klasse ganz unten) geordnet sind, entsteht wieder an gewisses Maß an Unordnug.
    Da man die Dateien im Arbeitsbereich innerhalb eines Ordners nicht einfach umordnen kann, habe ich den Dateien der ersten Basisklasse ein "A_" und der letzten abgeleiteten Basisklasse ein "F_" vorangestellt (entsprechend habe ich die Zwischenstufen wie MovingSprite bezeichnet.
    Nun habe ich zwar auch im Arbeitsbereich Ordnung, doch wirkt das ganze arg primitiv auf mich... andererseits fällt mir nichts besseres ein.

    Das Ziel dieser Beschreibung ist es nicht, speziell für dieses Problem eine Lösung zu finden, sondern aufzuzeigen, in welcher Ebene meine Probleme liegen.
    Ich werde mir zwar in den nächsten Tagen Marcus' Buch und noch ein anderen bestellen, doch wäre denkbar, wenn ich schon jetzt den einen oder anderen guten Tipp kriegen könnte.



  • das ist doch irgendwie nur ein IDE Problem, oder?

    Also ich habe dann unterordner wenn einige dateien zusammengehoeren und quasi eine eigene gruppe in der anwendung bilden.

    oder du verwendest nur die klassenansicht -> da hast du auch ne schoene liste...

    nunja, irgendwie habe ich die frage nicht ganz verstanden 😞



  • Hey Shade, du hast die Frage / mein Problem wirklich nicht so ganz verstanden 😉

    Kurz gesagt und "etwas" übertrieben: Sobald mein Code aus mehr als 3 Zeilen besteht, verliere ich den Überblick. Ich versuche zwar durch selbsterdachte Richlinien die Ordnung zu wahren und den Überblick zu behalten, doch das allein hilft mir noch nicht genug weiter.



  • Original erstellt von jetzt_heiße_ich_Smax:
    Hey Shade, du hast die Frage / mein Problem wirklich nicht so ganz verstanden 😉

    war mir nachdem ich meinen post geschrieben hatte auch klar 🙂

    Kurz gesagt und "etwas" übertrieben: Sobald mein Code aus mehr als 3 Zeilen besteht, verliere ich den Überblick.

    liegt es an dir, oder an deinem code?

    wenn es an dir liegt, hilft nur: ueben!
    wenn es an deinem code liegt, hilft nur: ueben!
    :p

    ne, also kleine Klassen nehmen, kurze funktionen, etc.
    du hast die grosse klasse ja schon gut aufgeteilt und hast einige kleine bekommen.

    wenn du jetzt noch implementierung von interface trennst (was du ja wahrscheinlich machst) hast du schoene kurze dateien...

    um den gesamt ueberblick zu bewaren, kannst du zB ein UML Diagramm nehmen. Hume hat beim Forumtreffen irgendein tool genannt, mit dem du aus bestehendem source code UML Diagramme erstellen lassen kannst (nur ich hab den namen vergessen 🤡

    ansonsten hilft ne gute IDE auch 🙂



  • Danke erstmal.

    MS VC++ 6.0 ist (noch) meine IDE. Ich hatte mal Visual Assist installiert, was auch vom "Workflow" einiges gebracht hat. Habe vor bald mal auf .net abzudaten und dann auch Visual Assist richtig zu lizensieren.
    Des weiteren habe ich noch mit den Makro-Funktionen der IDE etwas herumgespielt, bin jedoch zur Erkenntnis gelangt, dass es zu aufwändig wäre, selbst gute Makros zu erstellen.



  • Du hast das Problem relativ "distanziert" beschrieben. Ich denke, da dürfte es schwer sein, herauszufinden, woran es vielleicht konkret liegen könnte. Sag mal genauer was los ist. Was verstehst du unter einer "riesigen Klasse", unter einer kleinen Klasse,...?

    Wenn du die paar Dateien schon ordnen möchtest, dann kannst du die Klassenhierarchie ja gleich in der Verzeichnisstruktur wiederspiegeln. Ich halte das zwar - bei 12 Dateien - für Overkill, es erscheint mir aber besser als die Lösung mit den Prefixen.

    ...zeig doch auch mal typischen Code von dir. Vielleicht hat ein Teil deiner Übersichtsprobleme seinen Ursprung auf dieser Ebene.

    Dokumentierst du? ...und nutzt du ein Tool wie Doxygen?

    StaticObject -> StaticSprite -> MovingSprite -> AccSprite -> RotAccSprite -> FullSprite

    Diese Klassenhierarchie erscheint mir auf den ersten Blick etwas tief. Sag doch mal, inwiefern sich die einzelnen Klassen unterscheiden. Gibt es weitere Klassen, die von einer dieser Klassen erben?



  • Dokumentiere ich? Kommt darauf an, was du dir darunter vorstellst; mehr als einfach Kommentiere hier und da mache ich nicht.

    Sowas wie Doxygen habe ich bisher nicht verwendet, doch werde ich es spätestens morgen testen.

    Beschreibung der Klassen:
    StaticObject: Ein rechteckiges Objekt mit einer (x,y)-Position und einer (x,y)-Ausdehnung
    StaticSprite: + das rechteckige Etwas wird zum Sprite und kann animiert werden
    MovingSprite: + das Sprite kann sich bewegen
    AccSprite: + das Sprite kann beschleunigt werden...

    Etwas Code, Teil 1:

    class C_MovingSprite : public B_StaticSprite
    {
    public:
        //constructor and destructor
        C_MovingSprite();
        ~C_MovingSprite();
        //get value
        double getSpeed() const;
        double getVX() const;
        double getVY() const;
        //modify value
        void setVX(double newVX);
        void setVY(double newVY);
        void incVX(unsigned increase = 1);
        void incVY(unsigned increase = 1);
        void decVX(unsigned decrease = 1);
        void decVY(unsigned decrease = 1);
        void activateThrust();
        void deactivateThrust();
        //complex methods
        BorderState checkScreenBorders();
        void move();
    protected:
        BorderBehaviour borderBehaviour;
        BorderBehaviour borderState;
        bool motionlessStart;
        bool phThrust;
        bool phGravity;
        double vx;
        double vy;
        double xAtStart;
        double yAtStart;
        double speed;
    };
    

    Etwas Code, Teil 2:

    void B_StaticSprite::display()
    {
        SDL_Rect source = {sourceX, sourceY, width, height };
        SDL_Rect dest = {unsigned short(x), unsigned short(y)};
    
        //-------------------------------------------
        // set alpha of the surface
        //-------------------------------------------
    
        if(SDL_SetAlpha(surface, SDL_SRCALPHA, alpha) == ERROR)
        {
            printf("Couldn't set alpha: %s.\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    
        //-------------------------------------------
        // set color key of the surface
        //-------------------------------------------
    
        if(SDL_SetColorKey(surface, SDL_SRCCOLORKEY | SDL_SRCCOLORKEY , 
            SDL_MapRGB(SDL_GetVideoSurface()->format, 0,0,0)) == ERROR)
        {
            printf("Couldn't set colorkey: %s.\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    
        //-------------------------------------------
        // blit surface to back buffer
        //-------------------------------------------
    
        if(SDL_BlitSurface(surface, &source, SDL_GetVideoSurface(), &dest) == ERROR)
        {
            printf("Couldn't blit sprite: %s.\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    }
    


  • oh, dann liegts am Code.

    Immer wenn du eine C API verwendest, was du hier anscheinend tust, solltest du dir wrapper um die einzelnen funktionen schreiben - es muessen nicht immer klassen sein, du kannst auch ne funktion um ne funktion wrappen:
    der sinn ist der: du wirfst eine exception wenn die funktion fehlschlaegt.

    dann hast du die fehlerbehandlung aus dem direkten code raus -> viel besser lesbar!

    achja: ruf kein exit() auf -> das ruft dir naemlich keine dtors auf.

    deine klasse ist zu gross. ich kenn mich leider nicht aus mit grafikprogrammierung, deswegen kann ich keinen besseren vorschlag machen. (uU eine Klasse vector die member von sprite ist - auf jedenfall muss die klasse geteilt werden)

    btw: warum hast du nur nen stdctor?



  • Original erstellt von Shade Of Mine:
    **
    deine klasse ist zu gross.**

    Sieht IMHO garnicht so aus. Die Methodennamen lassen vermuten, dass es sich größtenteils um sehr einfache Methoden handelt. Entsprechend sollte die Klasse trotz der vielen Methoden recht klein sein.

    @ Tobias : Nenn doch mal konkret Zahlen, wie groß die Klassen und Methoden so sind.



  • Tobias, auf jeden Fall ist die Idee mit den A_ ... F_ furchtbar.

    Du mußt Dir mal überlegen, ob Deine Vererbungshierarchie wirklich richtig ist. Manchmal wird zu oft geerbt, weil Vererbung ja OOP ist. Aber das muß nicht so sein. Denk Dir noch mal die Klassen durch, ob ein RotAccSprite wirklich ein spezialisiertes AccSprite ist - oder ob sich RotAccSprite und AccSprite vielleicht durch ein Memberobjekt unterscheiden!

    Möglicherweise fährst Du besser, wenn Du komplexe Klassen als zusammengesetzte Dinge betrachtest (Stichwort: Komposition), z.B. kann eine Sprite-Klasse mit einer Bewegungsklasse (die dann Rot, Acc, Move sein kann) kombiniert werden und dadurch neue Fähigkeiten erlangen. Dann muß nicht jede neue Möglichkeit durch Vererbung gewonnen werden.

    Ist die alte Frage: "ist ein" oder "hat ein"?

    Nur mal als Anregung: ich könnte mir denken, daß ein Grafikobjekt eine Art "Bewegungsslot" hat, wo man Objekte reinsteckt, die die Bewegung realisieren. Diese Bewegungsobjekte können dann aus einer anderen Hierarchie stammen und die Berechnungen der neuen Koordinaten enthalten. So eine Art Visitor-Pattern. Stecke ich ein Objekt Rot und ein Objekt Move rein, so habe ich Drehung und Bewegung gleichzeitig.

    Kann man aus Entfernung natürlich nur schwer sagen, da man Deine Absicht nicht genau kennt, ebenso wenig Dein Designziel.



  • Original erstellt von Gregor:
    Sieht IMHO garnicht so aus. Die Methodennamen lassen vermuten, dass es sich größtenteils um sehr einfache Methoden handelt. Entsprechend sollte die Klasse trotz der vielen Methoden recht klein sein.

    nicht die methoden stoeren mich, sondern die member -> braucht eine klasse mit

    die effektiv 4 sachen kann:
    checkScreenBorders
    deactivateThrust
    activateThrust
    move

    wirklich
    10 member variablen?

    zuviele getter und setter - das stoert irgendwie... man kann ja fast jede variable setten 😞



  • ja, wir haben hier für jeweils X und Y:

    inc, dec, set, get.

    Eigentlich würden set und get genügen. Damit kannst Du den Rest bequem implementieren. Vielleicht noch ein SetDirection wo man x und(!) y Richtung angeben kann für den Speed. das macht dann schonmal 5 Methode statt 8. Das Klasseninterface ist damit zwar nichtmehr minimal, aber dafür kleiner und noch relativ komfortabel.



  • warum kriegt set/get double und inc/dec int?
    ich wuerde mit vektoren statt einzelnen koordinaten rechnen.
    kann man uebersichtlicher mit rechnen. und man kanns einfacher auf 3d erweitern.



  • Immer wenn du eine C API verwendest, was du hier anscheinend tust, solltest du dir wrapper um die einzelnen funktionen schreiben - es muessen nicht immer klassen sein, du kannst auch ne funktion um ne funktion wrappen:
    der sinn ist der: du wirfst eine exception wenn die funktion fehlschlaegt.

    Meinst du sowas:

    void TD_SetAlpha(surface, SDL_SRCALPHA, alpa)
    {
        if(SDL_SetAlpha(surface, SDL_SRCALPHA, alpha) == ERROR)
        {
            printf("Couldn't set alpha: %s.\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    }?
    

    achja: ruf kein exit() auf -> das ruft dir naemlich keine dtors auf.

    dtors = Destruktoren?

    @ Tobias : Nenn doch mal konkret Zahlen, wie groß die Klassen und Methoden so sind.

    sprites.h: 172 Zeilen
    sprites.cpp: 908 Zeilen

    Tobias, auf jeden Fall ist die Idee mit den A_ ... F_ furchtbar.

    Ja, stimmt.

    [...] Ist die alte Frage: "ist ein" oder "hat ein"?

    Darüber habe ich mir bei der Aufstellung der Klassenhierarchie auch schon meine Gedanken gemacht. Allerdings denke ich nun, dass ich zumindest die Bitmap/Anzeigefunktionalität nicht erben lassen brauche.

    Nur mal als Anregung: ich könnte mir denken, daß ein Grafikobjekt eine Art "Bewegungsslot" hat, wo man Objekte reinsteckt, die die Bewegung realisieren. Diese Bewegungsobjekte können dann aus einer anderen Hierarchie stammen und die Berechnungen der neuen Koordinaten enthalten. So eine Art Visitor-Pattern. Stecke ich ein Objekt Rot und ein Objekt Move rein, so habe ich Drehung und Bewegung gleichzeitig.

    Oh man, da kommen wieder diese komischen "Factories", "Singletons" und "Visitors" 😃 Tut mir Leid, aber dies Erklärung habe ich nicht recht verstanden. Ich hoffe das änder sich, sobald ich dein Buch lese.

    @ Shade

    Die 10 Variablen halte ich schon alle für erforderlich, doch die Setter/Getter und Increaser/Decreaser können eigentlich alle weg!
    Die habe ich in meinem neuen Code nur noch aus Kompatibilitätsgründen drin, werden aber bald entfernt.

    ich wuerde mit vektoren statt einzelnen koordinaten rechnen.
    kann man uebersichtlicher mit rechnen. und man kanns einfacher auf 3d erweitern.

    Dann hat man aber immer sowas wie motion.x bzw. motion.y.
    Außerdem finde ich es recht unhandlich, wenn Kräfteüberlagerungen (Gravitation, Kollision, Schub etc.) überlagern.



  • sprites.h: 172 Zeilen
    sprites.cpp: 908 Zeilen

    Das ist IMHO viel zu groß. Aber das hast du ja eh schon aufgeteilt, wenn ich das richtig mitgekriegt habe. Ist sicherlich Geschmackssache, aber ich würde darauf achten, keine Datei und keine Klasse zu kriegen, die größer als 250 Zeilen ist. Ich kann in größeren Dateien und Klassen nur schwer den Überblick behalten. Bei mir sind die Klassen und Dateien sogar noch viel kleiner: im Schnitt weniger als 50 Zeilen.



  • Original erstellt von jetzt_heiße_ich_Smax:
    Meinst du sowas:

    void TD_SetAlpha(surface, SDL_SRCALPHA, alpa)
    {
        if(SDL_SetAlpha(surface, SDL_SRCALPHA, alpha) == ERROR)
        {
            printf("Couldn't set alpha: %s.\n", SDL_GetError());
            exit(EXIT_FAILURE);
        }
    }
    

    ?

    nein, ich meine

    namespace TD {
    void SetAlpha(surface, SDL_SRCALPHA, alpa)
    {
        if(SDL_SetAlpha(surface, SDL_SRCALPHA, alpha) == ERROR)
        {
            throw SDLExceptopm(SDL_GetError());
           //bzw. was spezifischeres wie AlphaBlending(SDL_GetError())
        }
    }
    }
    

    dtors = Destruktoren?

    jep. ich keurze immer ab, sorry
    dtor == Destruktor
    ctor == Konstruktor
    copyctor == Kopierkonstruktor
    stdctor == Standard Konstruktor

    sprites.h: 172 Zeilen
    sprites.cpp: 908 Zeilen

    172 Zeilen fuer die interface definition sind viel
    und 908 zeichen fuer die implementation sind viel zu viel.

    bedenke: eine methode sollte nie laenger als 15 zeilen seien.

    Oh man, da kommen wieder diese komischen "Factories", "Singletons" und "Visitors" Tut mir Leid, aber dies Erklärung habe ich nicht recht verstanden. Ich hoffe das änder sich, sobald ich dein Buch lese.

    Jo, factory, Singleton und Visitor wird in seinem buch erklaert.
    kannst dir aber auch sachen im netz suchen. singleton und factory sind einfach zu verstehen.

    Die 10 Variablen halte ich schon alle für erforderlich, doch die Setter/Getter und Increaser/Decreaser können eigentlich alle weg!
    Die habe ich in meinem neuen Code nur noch aus Kompatibilitätsgründen drin, werden aber bald entfernt.

    wie gesagt: ich kenne mich mit grafikprogrammierung nicht aus...
    ich kann dir nicht sagen was warum wo nicht hingehoert, ich kann nur sagen, dass da zuviel ist.

    schau dir an, was deine klasse koennen muss (notfalls mach n simples UML Diagramm).
    muss die klasse setter anbieten? reicht es vielleicht wenn der sprite einmal positioniert wird, und dann nur noch ge'move't?
    reicht es vielleicht ein setVector anzubieten um bewegungsvectoren direkt zu setzen?
    etc.

    das interface einer klasse sollte moeglichst klein gehalten werden, alles was unnoetig ist kommt raus. manchmal muss man viel anbieten, aber oft kann man das wegdesignen...



  • Shade, Du darfst hier aber etwas nicht vergessen: bei einem Framework macht es durchaus Sinn, daß es sehr große Interfaces geben kann, um dem Aufrufer allerlei Schnickschnack in wenigen Schritten anzubieten. Der Aufrufer soll einige Sachen zentral finden. Hierdurch gibt es evtl. Teil-Redundanzen bei den Methoden, aber für den Anwender ist das von Vorteil.

    Auch die 15 Zeilen Regel kann ich so nicht unterstützen - bei der Programmierung von API-nahen Funktionen ist es durch Setzen und Abfragen von Optionen leicht möglich, daß man solche Grenzen sprengt. Es macht z.B. wenig Sinn einen Dialoghandler, der die Buttons nach einer Aktion umsetzt oder disabled/enabled, mit Gewalt auf so ein Limit zu beschneiden, nur damit die Regel erfüllt ist. Hier ist es oftmals übersichtlicher, wenn man die Anprogrammierung der API zusammen läßt. Ähnliches gilt auch für Schnittstellen oder z.B. für solche Grafik-Initialisierungen. Wenn typische Beispiele für eine Grafik-Lib in C geschrieben sind und dort bestimmte Schritte 30 Zeilen lang sind, so kann es hilfreicher sein dies weitgehend 1:1 zu übertragen, um die Analogie zu erhalten.



  • Original erstellt von jetzt_heiße_ich_Smax:
    Dann hat man aber immer sowas wie motion.x bzw. motion.y.
    Außerdem finde ich es recht unhandlich, wenn Kräfteüberlagerungen (Gravitation, Kollision, Schub etc.) überlagern.

    Hm. Trotzdem wäre eine Vektorklasse hilfreich, weil diese Kräfteüberlagerungen doch alle linear sind! D.h. Du kannst das problemlos in x und y zerlegen.

    Angenommen Du schreibst eine 2D-Vektorklasse (von mir aus auch 3D), so kannst Du die ganzen Operatoren dazu implementieren und das ganze wird dann zu position = richtung * zeit + startposition, ohne daß jemand sieht daß dahinter ein (x,y) oder gar (x,y,z) stecken. Halbiert Deinen ganzen Rechencode um den Faktor 2 in der Zeilenzahl und lagerst das aus. Das macht schon Sinn. Auch Drehungen kannst Du dann mit einer Maxtrixklasse realisieren, wo Vektor = Vektor * Matrix. Die eigentliche Arbeit verschwindet dann anderswo und Du benutzt nur noch die Mathematik innerhalb Deiner Wrapperklassen. Ebenso halbiert sich die Anzahl an Membervariablen, weil das x/y getrennt einfach wegwandert in ein vector m_Position statt int m_PosX int m_PosY.



  • Original erstellt von Marc++us:
    Vektor = Vektor * Matrix

    Iiihhh Zeilenvektoren 😃



  • hehe. normalerweise multipliziert man transformationen ja auch von links.


Anmelden zum Antworten