Was ist für euch guter Programmcode?



  • Optimizer schrieb:

    Das hat mit C nichts zu tun.

    Übertrieben gesagt schon. Wieso die Vorteile von C++ nicht nutzen, wenn sie denn gegeben sind. Und die aus dem objektorientierten Prinzip resultierende größere Wiederverwendbarkeit von C++- gegenüber C-Code ist ein entscheidender Vorteil. Also landet man wieder bei C.



  • simon.phoenix schrieb:

    Spaß beiseite, wieso programmiert mecnels nicht gleich C 👎?

    Nur zu deiner Information: intelligente Softwareentwicklung gab es auch schon vor C++, Java, OOP, der Komponentenorientierung und anderen neuen Techniken die so more "unified", "service-oriented" oder "model-driven" sind. Die wichtigsten Ideen (Modularisierung, Coupling-Cohesion, Information Hiding, Layering...) sind allesamt schon älter (werden allerdings immer wieder neu erfunden) und können auch in C angewendet werden.



  • simon.phoenix schrieb:

    Und die aus dem objektorientierten Prinzip resultierende größere Wiederverwendbarkeit

    ...ist ein Mythos der sich in 20 Jahren OOP nicht bewahrheitet hat. Die Annahme OOP = Wiederverwendbar ist in der Praxis schlicht und einfach falsch.



  • simon.phoenix schrieb:

    Optimizer schrieb:

    Das hat mit C nichts zu tun.

    Übertrieben gesagt schon. Wieso die Vorteile von C++ nicht nutzen, wenn sie denn gegeben sind. Und die aus dem objektorientierten Prinzip resultierende größere Wiederverwendbarkeit von C++- gegenüber C-Code ist ein entscheidender Vorteil. Also landet man wieder bei C.

    C-Code ist selbstverständlich modularisiert und bisweilen objektorientiert, einige einschlägigen Unix-Libs helfen dabei (die glib zB.).

    Das ganze Gewäsch von wegen "C unbrauchbar", "C++ toll" wird ja von der Realität nicht getragen. Die Wiederverwendbarkeit von durchschnittlichem Quelltext geht in beiden Sprachen -> 0, wenn man da nicht von Anfang an darauf Wert gelegt hat, und gegen viel, wenn doch (Bibliotheken eben). Die vielbeschworenen Vorteile von C++ belaufen sich insgesamt auf einen Haufen syntactic sugar, den nicht jeder lernen will -- und wenn man sich mal echte OOP ansehen will, dann nimmt man nicht C++ dafür.

    Sesch, immer dieses unqualifizierte C-Bashing. Furchtbar, das.



  • Daniel E. schrieb:

    Die Wiederverwendbarkeit von durchschnittlichem Quelltext geht in beiden Sprachen -> 0

    Warum? Es müssen ja nicht immer gleich umfangreich aufgezogene Libs sein. Paar stinknormale Klassensets finden auch in nem anderen Programm Verwendung. Und dass man darauf von Anfang an bedacht ist, hoffe ich doch.



  • @Hume:
    sorry, wenn ich jetzt Sachen nenne die schon genannt wurden, aber ich hab es nicht geschafft wirklich alle Posts durchzulesen 😞

    typedef unsigned int UInt32;
    class bit_vec_int_set
    

    Warum ist UInt32 groß und bit_vec_int_set klein?
    Welchem Namensschema folgst du?

    const unsigned char bits_set_in_byte_g[] =
    

    Wäre das in einem unnamed namespace nicht besser aufgehoben?
    Oder braucht man von aussen Zugriff?

    void bit_vec_int_set::resize(UInt32 newMaxVal)
    {
        UInt32 newWords = 1 + (newMaxVal / BITSPERWORD);
        if (newWords != words_)
        {
            UInt32* newSet = new UInt32[newWords];
            ::memcpy(newSet, set_, std::min(newWords, words_) * sizeof(UInt32));
            if (newWords > words_)
            {    // growing does not change size
                ::memset(newSet + words_, 0, (newWords - words_) * sizeof(UInt32));
            }
            else
            {    // shrinking may change size
                for (UInt32 i = newWords; i != words_; ++i)
                {
                    size_ -= countBitsOn(set_[i]);
                }
            }
            delete [] set_;
            set_ = newSet;
            words_ = newWords;
        }
        else
        {    // same capacity, clear elements greater than newMaxVal
            for (UInt32 i = newMaxVal; i != capacity() + 1; ++i)
            {
                remove(i);
            }
        }
    }
    

    Ich teile hier gerne in grow() und shrink() auf.

    inline void bit_vec_int_set::remove(UInt32 i)
    {
        assert(i <= capacity());
        if (test(i))
        {
            clr(i);
            --size_;
        }
    }
    

    Ohne zu wissen was test() macht (hab leider auf die schnelle nichts
    davon in deinem code gefunden) rate ich einmal:
    wenn bedingung erfüllt ist dann lösche, andernfalls nicht.
    Sicher, dass das löschen bei !test(i) erlaubt sein sollte und kein Fehler ist?
    dito bei insert()

    bit_vec_int_set bit_vec_int_set::getUnion(const bit_vec_int_set& other)  const
    {
        const bit_vec_int_set& smallerSet = words_ < other.words_ ? *this : other;
        const bit_vec_int_set& largerSet = &smallerSet == this ? other : *this;
        bit_vec_int_set newSet(largerSet);
        newSet.unite(smallerSet);
        return newSet;
    }
    

    Wäre das uU nicht besser als non member Funktion?

    Sonst ist mir nur aufgefallen: du verwendest manchmal
    foo_bar
    und manchmal
    fooBar
    als namensschema.



  • simon.phoenix schrieb:

    Daniel E. schrieb:

    Die Wiederverwendbarkeit von durchschnittlichem Quelltext geht in beiden Sprachen -> 0

    Warum? Es müssen ja nicht immer gleich umfangreich aufgezogene Libs sein. Paar stinknormale Klassensets finden auch in nem anderen Programm Verwendung. Und dass man darauf von Anfang an bedacht ist, hoffe ich doch.

    Warum es nicht richtig funktioniert, kann ich dir nicht sagen -- es scheint aber nun einfach so zu sein (gut -> 0 ist vielleicht übertrieben), wie ja auch Hume einen Beitrag drüber schreibt, so daß ich gar nichts mehr zu dem Thema hätte sagen brauchen, weil er ohne Zweifel kompetenter ist. Vielleicht sind die Problemstellungen einfach zu unterschiedlich, daß man einfach den gleichen Code mehrfach in vollständig anderen Umgebungen einsetzt -- in Bibliotheken versucht man, jede Eventualität abzudecken, in "echtem Code" nicht, weil man den Code möglichst kurz halten möchte, sonst steigt die Wartungs- und Anpassungsarbeit überproportional stark an, keine Ahnung.



  • Daniel E. schrieb:

    in Bibliotheken versucht man, jede Eventualität abzudecken, in "echtem Code" nicht, weil man den Code möglichst kurz halten möchte, sonst steigt die Wartungs- und Anpassungsarbeit überproportional stark an, keine Ahnung.

    Wie du schon sagtest, hängt stark von der Komplexität ab.

    @Hume:

    Könntest du

    ...ist ein Mythos der sich in 20 Jahren OOP nicht bewahrheitet hat. Die Annahme OOP = Wiederverwendbar ist in der Praxis schlicht und einfach falsch.

    etwas genauer erklären?



  • Ursprünglich ging es uns ja eigentlich darum, inwieweit grosszügige Kommentierung zu einem guten Programmcode beiträgt.
    Die MS-VC++.net 2003 IDE hat beispielsweise die nette Eigenart, Kommentare, die sie in der Nähe von Funktionsdeklarationen in einem Header entdeckt, als zusätzlichen Balloontip einzublenden, wenn man den Funktionsnamen irgendwo anders in einem Modul verwendet und sich nicht mehr ganz sicher ist, was eine Funktion genau machen soll oder kann, die man sich beispielsweise eine Woche vorher im Header ausgedacht hat.

    Bitte, ein Beispiel für eine gelungene Architektur eines etwas größeren Projektes, das, wie ich meine, auch sehr gut gelungen ist (trotz tief geschachtelter Schleifen) koennt ihr an diesem Header erkennen:

    /* SO UND DAS IST DER HEADER MEINER MECNELS TACTICS VORVERSION
       extra fuer meine Freunde bei www.c-sar.de
       Das Hauptprogramm und die Bibliothek, die sich um die Realisierung
       der Methoden kuemmert, die hier nur deklariert werden,
       machen zusammen stolze 1600 Codezeilen aus und ich verzichte
       darum darauf, sie mitzuliefern
    
       Compiliert ist das nette Spielchen in MS-VC++.net 2003
       und läuft in jedem Fall auf WinME, Win2000 und WinXP (da hab ich es
       jedenfalls selber getestet), wahrscheinlich auch Win98.
    
       Wie versprochen hier eine kurze Spielanleitung:
    
       Fuer diejenigen, die das Vorbildspiel Stratego schon kennen, sei kurz
       vorweg erwaehnt, dass die Wertigkeit der Spielsteine in dieser Version
       am Palettenfeld rechts zu erkennen ist, und zwar von links oben nach 
       rechts unten ansteigend.
       Das Erdbeereis entspricht der Fahne, der Devil dem Spion,
       die Fliege dem Aufklaerer, die Spinne dem Mineur und die Zecke
       entspricht den Bomben bei Stratego.
    
       Zuerst muesst ihr Eure Spielfiguren (dargestellt durch Tierfotos)
       in der Aufstellungszone des Spielfeldes (untere 4 Reihen) in Position
       bringen.
       Einfach mit der Maus von der Palette nehmen und auf dem Spielfeld
       platzieren. 
       Wenn ihr dann genug aufgestellt habt (40 Leutchen) (alternativ
       übernimmt das Aufstellen der Steine auch das Programm für Euch, wenn ihr auf
       Quickstart drückt)
       klickt ihr auf den unspektakulaeren OK-Button rechts unten.
    
       Der Computer ist gnaedig und lasst Euch den ersten Zug machen, ihr
       braucht eigentlich nur ein Start- und ein Zielfeld eingeben, damit der
       ausgefuehrt wird.
       Ihr muesst Eure Spielzuege immer mit einem  Klick irgendwo auf dem
       Spielfeld abschliessen, damit der Computer seinen Zug
       ausfuehrt.
       Das geht mitunter ziemlich flott vor sich, also solltet Ihr gut aufpassen,
       um Eure Chancen zu erhoehen.
       Die Zecken und das Erdbeereis koennen nicht bewegt werden.
       Die Fliegen koennen fast beliebig weit ziehen, probiert es einfach aus.
       Alle anderen Spielsteine koennen immer nur um ein Feld bewegt werden.
    
       Abwechselnd mit dem Computer macht Ihr Eure Zuege, bis einer von Euch
       das Erdbeereis des Gegners erobert.
    
       Wie bereits erwaehnt haben die Spielfiguren unterschiedliche Kampfwerte, von
       denen es abhaengt, wer im Falle eines Gefechtes am Spielfeld bleibt.
       Prinzipiell gilt die Ordnung, die im Palettenfeld herrscht, ganz links oben,
       das Erdbeereis, kann also von einem beliebigen zugbegabten Spielstein erobert
       werden, waehrend die Bekanntschaft mit einer Zecke fast immer den Angreifer
       ins Jenseits schickt (mit Ausnahme der Spinnen, die Zecken fressen)
       Wichtig ist dann noch, dass der Hunter, der staerkste zugfaehige Stein,
       nur vom Devil geschlagen werden kann, waehrend der Devil gegen jeden anderen
       zugfaehigen Stein den Kuerzeren zieht)
    
       ---------------------------ICH WAR NOCH NIE SONDERLICH GUT----------------
       ---------------------------BEIM ERKLAEREN, ABER FUERS ERSTE---------------
       ---------------------------IST DAS ALLES, WAS MIR EINFAELLT----------------
    
    	Hoffentlich gefaellt dem einen oder anderen von Euch das Game,
    	ich weiss, eine Menge Details waeren sicher noch zu verbessern,
    	aber da hoffe ich auch auf Verbesserungsvorschlaege von Euch
    
    	----------------------------------------------------------------------------*/
    
    // So und hier ist der Header, damit Ihr Euch in etwa vorstellen koennt,
    // wie die Architektur dieses Programmes aussieht
    
    // Wird an allen Ecken und Enden fuer Berechnungen benoetigt
    class Vektor{
    public:
    	short x,y;
    	Vektor();
    	Vektor(short xx,short yy);
    	bool operator==(Vektor Vergleich);
    	Vektor operator-();
    	Vektor operator+(Vektor Summand);
    	Vektor operator-(Vektor Minuend);
    	short operator*(Vektor Faktor1);
    };
    
    Vektor operator*(Vektor Faktor1,short Faktor2);
    
    // Ein Menge, in der mehrere Vektoren die Elemente sein koennen
    class Vektormenge{
    	unsigned Zuegezahl; // defensiver Programmierstil faengt schon damit an
    						// dass man nicht einfach ueberall int hinschreibt,
    						// wo Ganzzahlen benoetigt werden, sondern auch ueberlegt
    						// ob die Werte ueberhaupt negativ sein koennen
    	Vektor* Zuege;
    public:
    	Vektormenge();
    	Vektormenge(const Vektormenge& zu_kopieren);
    	~Vektormenge();
    	bool AddZug(Vektor Hinzu);
    	bool IsInside(Vektor Gesucht);
    	unsigned GetZuegezahl();
    
    	// Liefert einen ganz speziellen Zug im Zuegearray zurueck
    	// sollte erst nach Abfrage mit GetZuegezahl() aufgerufen
    	// werden
    	Vektor GetZug(unsigned ZugNummer);
    };
    
    #include<windows.h>
    
    // Die (hoffentlich) massgeschneiderte Spielsteinklasse
    class Piece{
    	public:
    	bool known;
    	short besitzer;
    	short value;
    	Vektormenge* Zuegearray; // Speichert die Adresse einer Vektormenge
    							 // in der die fuer diesen Stein prinzipiell
    							 // moeglichen Zuege gespeichert sind
    	HBITMAP* Ansichten[3];
    	UINT AnzahlAnsichten;
    	char* my_Name;
    	Piece();
    
    	// Gibt -1 zurueck, falls kein Zugarray initialisiert wurde,
    	// ansonsten die Zahl der im Zugarray gespeicherten moeglichen
    	// Zuege
    	long GetMoveAmount();
    
    	// Befragt den Stein, ob er einen gewissen Zug
    	// ausfuehren kann
    	bool CanYouDo(Vektor Gefragt);
    
    	// Liefert Eigenen Wert
    	short GetValue();
    
    };
    
    // eine Feld ist eine Spezialisierung eines Vektors
    class Feld:public Vektor{
    	public:
    	UINT Laenge;
    	Piece** Hiesige_Steine;
    	UINT maximal;
    	UINT aktuell;
    	HBITMAP* Falls_leer;
    	HBITMAP* Es_geht_ab;
    	HWND my_hwnd;
    	Feld();
    
    	// nicht vergessen_baseID wird zum Zwecke der
    	// Zugehoerigkeit zu Spielfeld oder Palettenfeld
    	// vergeben (irgend ein vierstelliger Wert)
    	// BITTE ERST AUFRUFEN, wenn die noetigen Werte
    	// eingegeben wurden (geht leider nur muehsam
    	// und manuell)
    	bool Create(HINSTANCE hinst,HWND hpar,UINT baseID,
    		UINT pixtoleft,UINT pixtoup);
    
    	// Diese Funktion reserviert sogleich auch den
    	// benoetigten Platz fuer die Zeiger auf die Steine
    	// (wobei geplant ist, dass die Zeiger auch auf NULL
    	// zeigen koennen)_Ausserdem zeigen alle Zeiger erstmal
    	// auf NULL, das heisst, man kann nur einmal SetMaximal
    	// aufrufen, um das Programm nicht durcheinander zu bringen
        bool SetMaximal(UINT maxkapaz);
    
    	// Ein Aufruf dieser Funktion liefert true zurueck, wenn
    	// das Uebernehmen des Steines geklappt hat (er also noch
    	// Platz gehabt hat, ansonsten false), die Steine werden
    	// uebereinander gestapelt
    	bool UebernimmStein(Piece* NeuerStein);
    
    	// Diese Funktion nimmt quasi den Stein von ganz oben vom
    	// Stapel, d.h. setzt erstens den entsprechenden Zeiger auf
    	// NULL, reduziert 'aktuell' wieder um 1 (sofern moeglich,
    	// falls nicht: NULL-Zeiger retour. (!!! Wird der Rueckgabewert
    	// nicht gespeichert, geht dem Spiel praktisch ein Spielstein
    	// verloren !!! - UebernimmStein und SteinWegnehmen arbeiten wie
    	// ein FILO Stapel zusammen)
    	Piece* SteinWegnehmen();
    
    	// WENN ALLES KORREKT INITIALISIERT WURDE, DANN KANN ENDLICH
    	// ZUM ERSTEN MAL DIE FUNKTION Draw() aufgerufen werden,
    	// natuerlich weiss das Feld selbst, was wohin gezeichnet wer-
    	// den soll, sonst haette ich mir ja die Mordsinitialisierungs-
    	// funktion der Klasse MTactics gleich schenken koennen
    	void Draw(bool spielt_sich_ab=false);
    };
    
    // INNERHALB DER ANWENDUNGSKLASSE SPIELT SICH PRAKTISCH ALLES
    // AB, SIE IST DIE EIGENTUEMERIN UND OBERSTE VERWALTERIN DES
    // GESAMTEN PROGRAMMS, ein riesiger Container mit wenigen,
    // ziemlich grossen Funktionen, die dafuer aber auch entsprechend
    // maechtig sind
    class MTactics{
    	HINSTANCE hinzz; // Von Create erledigt
    	HBITMAP verwendet[28]; // Von Create erledigt
    	Vektormenge Zuegearray[3]; // Von Create erledigt
    	Piece Steine[80]; // Von Create erledigt
    	Feld Spielfeld[10][10],Palettenfeld[3][4]; // Von Create erledigt
    	HWND vergrPlr,vergrCpu,plrText,cpuText; // Von Create erledigt
    	HWND Startbutton,Palbutton,Qustartbutton; // Von Create erledigt
    	HWND hauptfenster; // Von Create erledigt
    	char stnamen[12][12]; // Von Create erledigt
    	char no_selection[5]; // Von Create erledigt
    	Vektor hilfsvektorarray[2][210]; // in [0] werden die Start-
    									// felder gespeichert
    									// in [1] die Zugehoerigen
    									// Zielfelder
    
    	short Spielphase; // 1 fuer Aufstellphase, 2 fuer Spielphase
    	short NextPhase; // Mitunter benoetigt
    
    	Piece* plrchosen; // In diesen Variablen werden Spielsteine
    	Piece* cpuchosen; // gespeichert, die gerade ausgewaehlt
    					  // wurden
    	Piece* winnerx; // Hier wird der Gewinner einer Begegnung zwischengespeichert
    	Vektor Startfeld; // hier wird gespeichert, von wo der Spieler gestartet
    					  // ist, um spaeter einen Zug auf seine Zulaessigkeit
    					  // pruefen zu koennen. Falls -1/-1, dann gibt es noch kein
    					  // Startfeld
    	Vektor Zielfeld; // Zur Zwischenspeicherung gedacht
    	bool fuenfunbekannt; // Eine Variable, die die Fairness der KI sicherstellen
    						 // soll
    public:
    	bool DoFast; // noetig, um die Bedienung des Games mit der Maus
    				 // etwas komfortabler zu gestalten
    	WPARAM reserve;
    
    	// Diese Funktion ist geradezu spektakulaer (einige hundert
    	// Codezeilen)
    	bool Create(HINSTANCE instanz,HWND hauptf);
    
    	// Anzeigefunktion, die das gesamte Game auf dem Bildschirm abbilden kann,
    	// wird aber nur ein einziges Mal, und zwar von Create, aufgerufen
    	bool ShowAll();
    
    	// Diese Funktion wird auch nicht gerade simpel - Verarbeitet alle nur
    	// denkbaren Mausklicks, die passieren koennen
    	bool ProcessClick(WPARAM wParam);
    
    	// Kuerzt die Aufstellprozedur ab
    	bool QuickStart();
    
    	// Show_Selected kuemmert sich um die Anzeigen im
    	// Vergroesserungsfenster sowie um die Texte darunter
    	bool ShowSelected();
    
    	// Stelle Deine Figuren auf _ nach einem streng geheimen (lol) Algorithmus
    	// stellt hier der Computer seine Steine auf, oder auch die des
    	// Spielers, wenn der auf Quickstart geklickt hat
    	bool CpuAufstellen(short addf=40);
    
    	// Spieler_zieht() befasst sich mit dem Spielzug des Spielers
    	// d.h. interpretiert die Mauseingaben und leitet entsprechend
    	// an die Funktionen ZugMoeglich() und ExecuteMove() weiter
    	bool Spieler_zieht(WPARAM wParam);
    
    	// Computer_zieht() befasst sich mit der Berechnung des Computerzuges
    	// das ist de facto die AI - Funktion, hat aber die beiden Helferlein
    	// StrongAI() und FleeAI(), die sie unterstuetzen
    	void Computer_zieht();
    
    	// Diese Funktion ueberprueft, ob die Aufstellung des Spielers zumindest
    	// das Eis enthaelt, nur dann
    	// wird das Spiel ueberhaupt gestartet, klasse Beispiel fuer Redundanz
    	// extra kurze Funktion die so gut wie nie gebraucht wird
    	bool Checkvalid();
    
    	// wichtige Funktion - Findet heraus, ob ein Zug mit einem Stein moeglich ist
    	// - unter Beruecksichtigung der aktuellen Spielfeldsituation, da
    	// sich zwar jede Fliege beispielsweise um den Vektor (9,0) prinzipiell
    	// bewegen kann, koennte doch die Spielfeldbegrenzung oder ein Hindernis
    	// dafuer sorgen, dass der Zug trotzdem nicht moeglich ist
    	// Ein Zug, den diese Funktion fuer regelkonform befindet, kann getrost
    	// zur spaeteren Beurteilung durch eine KI-Funktion gespeichert werden
    	bool ZugMoeglich(Vektor,Piece*);
    
    	// Funktion, die den angegebenen Zug ausfuehrt, sollte natuer-
    	// lich nur Zuege in der Vektor Variablen erhalten, die zuvor
    	// auf Zulaessigkeit ueberprueft wurden, um Zugriffe auf den
    	// NULL Zeiger und dergleichen zu verhindern
    	bool ExecuteMove(Vektor,Piece*);
    
    	// Funktion, die sich wirklich starke Computerzuege einfallen laesst
    	// gibt -1 zurueck, wenn nur noch schwache Zuege moeglich sind
    	short StrongAI(short m);
    
    	// Unterstuetzt das Programm dabei, angemessene Fluchtzuege zu machen,
    	// wenn seine Steine angegriffen werden
    	short FleeAI(short m);
    };
    
    // Wird zum Control_ID vergeben benoetigt
    #define SPIELFELDID 1000 
    #define PALETTENFELDID 2000
    #define PALBUTTONID 3000
    #define VERGRCPUID 4000
    #define VERGRHUMID 5000
    #define VERGRPLRID 5000
    #define TEXTCPUID 6000
    #define TEXTPLRID 7000
    #define TEXTSIZE 33
    #define STARTBUTTONID 8000
    #define EINZELFELDGROESSE 48
    #define HUMPLR 1
    #define CPUPLR 2
    #define LARGESIZE 130
    #define BUTTONSIZE 100
    #define AUFSTELLZONE 6
    #define QUICKSTARTID 9000
    

    Und das Game selber koennt ihr dann von meiner Free Webspace Seite, die in meinem Profil steht, einfach herunterladen und ausprobieren, falls ihr immer noch daran zweifelt, dass viele Kommentare und die eine oder andere verschachtelte Schleife einem grossen Programm nur schaden können.

    Viel Spass!



  • schonmal was von echten kosntanten in c++ gehört? nein? so langsam wundert mich hier gar nix mehr.... 😮



  • Shade Of Mine schrieb:

    @Hume:
    sorry, wenn ich jetzt Sachen nenne die schon genannt wurden, aber ich hab es nicht geschafft wirklich alle Posts durchzulesen 😞

    typedef unsigned int UInt32;
    class bit_vec_int_set
    

    Warum ist UInt32 groß und bit_vec_int_set klein?
    Welchem Namensschema folgst du?

    Ich persönlich bevorzuge Camel-Case. Die Klasse ist allerdings Teil eines Projekts, dass dem Standard-C++ Schema folgt. Leider schaltet mein Gehirn immer mal wieder auf "automatik" und da wir bei dem Projekt keine gegenseitigen Reviews gemacht haben, ist da wohl zwischendurch immer mal wieder Camel-Case reingerutscht.

    Shade Of Mine schrieb:

    const unsigned char bits_set_in_byte_g[] =
    

    Wäre das in einem unnamed namespace nicht besser aufgehoben?

    Welchen Vorteil hätte das? Oder meinst du damit auch gleich Definition in der cpp-Datei?

    Shade Of Mine schrieb:

    void bit_vec_int_set::resize(UInt32 newMaxVal)
    {
        [...]
    }
    

    Ich teile hier gerne in grow() und shrink() auf.

    Guter Punkt.

    Shade Of Mine schrieb:

    Ohne zu wissen was test() macht

    test(i) liefert 0, falls i nicht Element der Menge und hat seinen Namen, weil es testet ob das zu i passende Bit gesetzt ist oder nicht.
    Es wäre sicher einfacher, wenn ich remove und insert über find() implementieren würde und nicht über das low-level test().

    Shade Of Mine schrieb:

    bit_vec_int_set bit_vec_int_set::getUnion(const bit_vec_int_set& other)  const
    {
        const bit_vec_int_set& smallerSet = words_ < other.words_ ? *this : other;
        const bit_vec_int_set& largerSet = &smallerSet == this ? other : *this;
        bit_vec_int_set newSet(largerSet);
        newSet.unite(smallerSet);
        return newSet;
    }
    

    Cool. Das habe ich mir gestern nachdem ich den Code gepostet hatte (und damit seit einem halben Jahr zum ersten Mal wieder draufgeschaut habe) auch als
    erstes gedacht.



  • Mecnels schrieb:

    Bitte, ein Beispiel für eine gelungene Architektur eines etwas größeren Projektes, das, wie ich meine, auch sehr gut gelungen ist (trotz tief geschachtelter Schleifen) koennt ihr an diesem Header erkennen [...]

    Hoi,

    ich rate dir _dringend_, statt #define const und enum zu benutzen. Außerdem würde ich keine rohen Arrays und Zeiger verwenden.

    Vektormenge* Zuegearray
    

    Sowas ist einfach ganz böse und immer wieder schuld an late-night-debugging-sessions.
    Versuch statt dessen lieber std::vector oder boost::array und setzt dir noch eine Indexprüfung in den op[].

    Die Signaturen für die Operatoren '+' und '-' sind schlicht falsch. Ein Buch, was mir diesbzgl. sehr auf die Sprünge geholfen hat, ist "Effektiv C++ programmieren" von Scott Meiers. Sehr lesenswert.



  • Mecnels schrieb:

    // Wird an allen Ecken und Enden fuer Berechnungen benoetigt
    class Vektor{
    //...
    }
    

    Uninteressant...
    Daher, dass Vektor ein abstrakter Begriff ist, solltest du betonen, dass du einen geometrischen meinst.

    // defensiver Programmierstil faengt schon damit an
    // dass man nicht einfach ueberall int hinschreibt,
    // wo Ganzzahlen benoetigt werden, sondern auch ueberlegt
    // ob die Werte ueberhaupt negativ sein koennen
    

    Ist das ein Statement für uns oder steht das wirklich so im Quelltext?

    Was soll die Klasse Vektormenge? Ein schlecht implementierter Container???

    #include <windows.h>
    

    Gehört ganz oben hin.

    Zu der Piece-Klasse:

    Wieso immer die antiquierte Trennung von Array und Arraygröße? Wieso nicht gleich ein Container?
    Außerdem sind die Hälfte der Kommentare nutzlos.

    Piece** Hiesige_Steine;
    

    😮

    Die Funktionsbeschreibungen sind Romane ("bitte", "nicht vergessen",...)...

    // ein riesiger Container mit wenigen,
    // ziemlich grossen Funktionen, die dafuer aber auch entsprechend
    // maechtig sind
    

    aha.

    [....]

    Deine Namenskonventionen sind lustig. Mal deutsch, mal englisch, mal gemischt.

    // Wird zum Control_ID vergeben benoetigt
    #define SPIELFELDID 1000 
    #define PALETTENFELDID 2000
    #define PALBUTTONID 3000
    #define VERGRCPUID 4000
    #define VERGRHUMID 5000
    #define VERGRPLRID 5000
    #define TEXTCPUID 6000
    #define TEXTPLRID 7000
    #define TEXTSIZE 33
    #define STARTBUTTONID 8000
    #define EINZELFELDGROESSE 48
    #define HUMPLR 1
    #define CPUPLR 2
    #define LARGESIZE 130
    #define BUTTONSIZE 100
    #define AUFSTELLZONE 6
    #define QUICKSTARTID 9000
    

    #defines sind böse, nimm Konstanten. Außerdem gehören die ganz oben hin.



  • simon.phoenix schrieb:

    @Hume:

    ...ist ein Mythos der sich in 20 Jahren OOP nicht bewahrheitet hat. Die Annahme OOP = Wiederverwendbar ist in der Praxis schlicht und einfach falsch.

    Ein Grund: Die allerwenigsten Klassen sind einsame Inseln die ohne Kontext eingesetzt werden können. Vielmehr ermutigt einen die OOP ja dazu, Programme als Kolaboration von Objekten zu implementieren.
    Auf der anderen Seite bieten die OO-Mittel von C++ aber keinerlei Möglichkeiten benötigten Kontext in programmlesbarer Form explizit zu spezifizieren.
    Das führt dann dazu, dass sich "einfache" Klassen wie Container andere reine Value-Types einfach wiederverwenden lassen, da sie keinerlei spezielle Abhängigkeiten besitzen. Die Wiederverwendung von Domainabstraktionen artet hingegen meist in eine ewige Fummelarbeit aus in der Abhängigkeiten mühsam Stück für Stück herausgearbeitet und in Form von z.B. Interfaces sichtbar gemacht werden müssen.
    Wenn du dann noch Domain-Details im Code fest verdrahtest hast, ist meist alles aus, da genau solche Details gerade von Projekt zu Projekt unterscheiden. D.h. selbst wenn du in den sauren Apfel beist und neben einem Foo gleich auch noch ein Fred, Heinz und Karl in deinem neuen Projekt aktzeptierst (obwohl du eigentlich nur Foo brauchst), garantiert dir keiner, dass die für Foo in Projekt X getroffenen Annahmen in Projekt Y immernoch gelten. Hier die nötige Flexibilität zu erlauben erfordert viel arbeit und kommt keinesfalls alleine. Das ist also kein Nebenprodukt, dass dank OOP abfällt.

    Das ist ja z.B. genau der Punkt wo die Komponentenorientierung punkten will (z.B. CORBA Komponenten usw.) Hier versucht man die Abhängigkeiten in Metadaten zu verpacken und dadurch eine Wiederverwendung zumindestens auf Komponentenebene (meist mehr als eine einzelne Klasse) zu erlangen.

    Es gibt noch viele andere Gründe warum die Gleichung OOP = gute Wiederverndbarkeit nicht automatisch aufgeht. Die Tatsache ist auf jeden Fall durch Zahlen belegt (einige Sachen findet man z.B. in McConnells "Code Complete") und Gründe dafür kann man eigentlich in jedem neueren OOP-Buch nachlesen. Eine Suche im Netz liefert ebenfalls viele Hinweise.



  • Danke Hume für die Erklärung. Muss da wohl etwas von meiner blauäugigen Ansicht abrücken 🙄

    D.h. man verwendet OOP einfach, erwartet aber kein Allheilmittel von ihr? Oder h.d. es ist egal ob man prozedural oder objektorientiert programmiert :p?



  • Mecnels schrieb:

    Und das Game selber koennt ihr dann von meiner Free Webspace Seite, die in meinem Profil steht, einfach herunterladen und ausprobieren, falls ihr immer noch daran zweifelt, dass viele Kommentare und die eine oder andere verschachtelte Schleife einem grossen Programm nur schaden können.

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 10000 LOC an.



  • MaSTaH schrieb:

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 10000 LOC an.

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 100000 LOC an.
    🙂



  • Gregor schrieb:

    MaSTaH schrieb:

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 10000 LOC an.

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 100000 LOC an.

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 1000000 LOC an.



  • Gregor schrieb:

    MaSTaH schrieb:

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 10000 LOC an.

    LOL, groß? Das ist nicht groß! Groß fängt für mich bei > 100000 LOC an.

    Deswegen habe ich ja gesagt "fängt an" 🙂 . Ich habe meist keinen Einblick in das komplette Projekt und dementsprechend kann ich auch nicht schätzen wieviele LOC es insgesamt beherbergt. Ich habe nur mal grob geschätzt wieviel in meiner aktuellen Library mittlerweile schon so angefallen sein könnte. Steckt noch in den Kinderschuhen, aber ich würde sie schon jetzt als groß (besser gesagt umfangreich) bezeichnen.



  • @Mecnels:

    // Wird an allen Ecken und Enden fuer Berechnungen benoetigt
    

    Unnötiger Kommentar. Sagt nämlich nichts aus. Ich weiss jetzt nur
    Vektor ist wichtig, aber warum es dass ich, weiss ich nicht.
    Und was es ist auch nicht.

    class Vektor{
    public:
        short x,y;
        Vektor();
        Vektor(short xx,short yy);
        bool operator==(Vektor Vergleich);
        Vektor operator-();
        Vektor operator+(Vektor Summand);
        Vektor operator-(Vektor Minuend);
        short operator*(Vektor Faktor1);
    };
    

    diese operatoren sollten (mit ausnahme des unären -) non member funktionen sein.

    // Ein Menge, in der mehrere Vektoren die Elemente sein koennen
    

    Da fällt mir sofort auf: was macht Vecktormenge besser als std::vector ?
    Warum kein standard Container?

    bool AddZug(Vektor Hinzu);
    

    Was macht das bool? true/false bei success fehlschlagen?
    Dafür haben wir exception...

    bool IsInside(Vektor Gesucht);
    

    wäre find() nicht vielleicht besser?
    und sollte es nicht vielleicht const sein?

    warum aufeinmal englisch?

    unsigned GetZuegezahl();
    

    wäre das hier const nicht vielleicht besser?

    Vektor GetZug(unsigned ZugNummer);
    

    Wäre dafür der op[] nicht besser geeignet? Und const wäre es auch nicht schlecht.

    Vektormenge* Zuegearray; // Speichert die Adresse einer Vektormenge
                                 // in der die fuer diesen Stein prinzipiell
                                 // moeglichen Zuege gespeichert sind
    

    warum ein zeiger?

    // eine Feld ist eine Spezialisierung eines Vektors
    

    unnötiger kommentar. Aber was ist ein Feld und _warum_ ist es
    die Spezialisierung eines vektors?

    Piece** Hiesige_Steine;
    

    Wäre hier ein Container nicht angebracht?
    Zeiger auf Zeiger sind oft sehr häßlich zu verwenden...

    HBITMAP* Falls_leer;
        HBITMAP* Es_geht_ab;
    

    Namen sagen mir garnichts.

    // nicht vergessen_baseID wird zum Zwecke der
        // Zugehoerigkeit zu Spielfeld oder Palettenfeld
        // vergeben (irgend ein vierstelliger Wert)
        // BITTE ERST AUFRUFEN, wenn die noetigen Werte
        // eingegeben wurden (geht leider nur muehsam
        // und manuell)
    

    Klingt übel und klingt nach refactoring

    // Diese Funktion reserviert sogleich auch den
        // benoetigten Platz fuer die Zeiger auf die Steine
        // (wobei geplant ist, dass die Zeiger auch auf NULL
        // zeigen koennen)_Ausserdem zeigen alle Zeiger erstmal
        // auf NULL, das heisst, man kann nur einmal SetMaximal
        // aufrufen, um das Programm nicht durcheinander zu bringen
    

    Klingt auch übel. Klingt sehr nach schwer zu findenden Bug wenn man
    es doch tut.
    Und klingt auch nach: der name SetMaximal passt einfach nicht.

    // Diese Funktion nimmt quasi den Stein von ganz oben vom
        // Stapel, d.h. setzt erstens den entsprechenden Zeiger auf
        // NULL, reduziert 'aktuell' wieder um 1 (sofern moeglich,
        // falls nicht: NULL-Zeiger retour. (!!! Wird der Rueckgabewert
        // nicht gespeichert, geht dem Spiel praktisch ein Spielstein
        // verloren !!! - UebernimmStein und SteinWegnehmen arbeiten wie
        // ein FILO Stapel zusammen)
    

    Klingt echt kompliziert und bug anfällig.

    // WENN ALLES KORREKT INITIALISIERT WURDE, DANN KANN ENDLICH
        // ZUM ERSTEN MAL DIE FUNKTION Draw() aufgerufen werden,
        // natuerlich weiss das Feld selbst, was wohin gezeichnet wer-
        // den soll, sonst haette ich mir ja die Mordsinitialisierungs-
        // funktion der Klasse MTactics gleich schenken koennen
        void Draw(bool spielt_sich_ab=false);
    

    Der Parameter ist mir nicht klar
    Und ein enormer Aufwand bis ich Draw() aufrufen kann, ist das auch.

    Sollte man vielleicht etwas vereinfachen...

    // INNERHALB DER ANWENDUNGSKLASSE SPIELT SICH PRAKTISCH ALLES
    // AB, SIE IST DIE EIGENTUEMERIN UND OBERSTE VERWALTERIN DES
    // GESAMTEN PROGRAMMS, ein riesiger Container mit wenigen,
    // ziemlich grossen Funktionen, die dafuer aber auch entsprechend
    // maechtig sind
    

    Hilft mir nicht viel um zu verstehen was da abgeht.

    Wenn ich eine Klasse sehe, gehe ich immer davon aus, dass sie wichtig ist...

    HINSTANCE hinzz; // Von Create erledigt
        HBITMAP verwendet[28]; // Von Create erledigt
        Vektormenge Zuegearray[3]; // Von Create erledigt
        Piece Steine[80]; // Von Create erledigt
        Feld Spielfeld[10][10],Palettenfeld[3][4]; // Von Create erledigt
        HWND vergrPlr,vergrCpu,plrText,cpuText; // Von Create erledigt
        HWND Startbutton,Palbutton,Qustartbutton; // Von Create erledigt
        HWND hauptfenster; // Von Create erledigt
        char stnamen[12][12]; // Von Create erledigt
        char no_selection[5]; // Von Create erledigt
        Vektor hilfsvektorarray[2][210]; // in [0] werden die Start-
                                        // felder gespeichert
                                        // in [1] die Zugehoerigen
                                        // Zielfelder
    
        short Spielphase; // 1 fuer Aufstellphase, 2 fuer Spielphase
        short NextPhase; // Mitunter benoetigt
    
        Piece* plrchosen; // In diesen Variablen werden Spielsteine
        Piece* cpuchosen; // gespeichert, die gerade ausgewaehlt
                          // wurden
        Piece* winnerx; // Hier wird der Gewinner einer Begegnung zwischengespeichert
        Vektor Startfeld; // hier wird gespeichert, von wo der Spieler gestartet
                          // ist, um spaeter einen Zug auf seine Zulaessigkeit
                          // pruefen zu koennen. Falls -1/-1, dann gibt es noch kein
                          // Startfeld
        Vektor Zielfeld; // Zur Zwischenspeicherung gedacht
        bool fuenfunbekannt; // Eine Variable, die die Fairness der KI sicherstellen
                             // soll
    public:
        bool DoFast; // noetig, um die Bedienung des Games mit der Maus
                     // etwas komfortabler zu gestalten
        WPARAM reserve;
    

    Bo ey. Soviele Variablen, teils Zeiger teils nicht. Und ein paar wahnsinns arrays.
    Ne, die Klasse will ich nicht warten müssen...

    bool Create(HINSTANCE instanz,HWND hauptf);
    

    Warum eigentlich immer Create() und nicht im Ctor?

    // Anzeigefunktion, die das gesamte Game auf dem Bildschirm abbilden kann,
        // wird aber nur ein einziges Mal, und zwar von Create, aufgerufen
        bool ShowAll();
    

    Was liefert sie denn zurück? Und warum ist sie public wenn nur Create sie aufrufen darf?

    // Kuerzt die Aufstellprozedur ab
        bool QuickStart();
    

    ??
    sagt mir garnix. und was macht das bool?

    // Stelle Deine Figuren auf _ nach einem streng geheimen (lol) Algorithmus
        // stellt hier der Computer seine Steine auf, oder auch die des
        // Spielers, wenn der auf Quickstart geklickt hat
        bool CpuAufstellen(short addf=40);
    

    wasmacht addf?

    // Computer_zieht() befasst sich mit der Berechnung des Computerzuges
        // das ist de facto die AI - Funktion, hat aber die beiden Helferlein
        // StrongAI() und FleeAI(), die sie unterstuetzen
        void Computer_zieht();
    

    Eine ganze AI in 3 funktionen... Ist sicher riesig.

    // Diese Funktion ueberprueft, ob die Aufstellung des Spielers zumindest
        // das Eis enthaelt, nur dann
        // wird das Spiel ueberhaupt gestartet, klasse Beispiel fuer Redundanz
        // extra kurze Funktion die so gut wie nie gebraucht wird
        bool Checkvalid();
    

    Eis?

    #define SPIELFELDID 1000
    #define PALETTENFELDID 2000
    #define PALBUTTONID 3000
    #define VERGRCPUID 4000
    #define VERGRHUMID 5000
    #define VERGRPLRID 5000
    #define TEXTCPUID 6000
    #define TEXTPLRID 7000
    #define TEXTSIZE 33
    #define STARTBUTTONID 8000
    #define EINZELFELDGROESSE 48
    #define HUMPLR 1
    #define CPUPLR 2
    #define LARGESIZE 130
    #define BUTTONSIZE 100
    #define AUFSTELLZONE 6
    #define QUICKSTARTID 9000
    

    Schonmal was von const gehört?


Anmelden zum Antworten