ZFX
ZFX Neu
Home
Community
Neueste Posts
Chat
FAQ
IOTW
Tutorials
Bücher
zfxCON
ZFXCE
Mathlib
ASSIMP
NES
Wir über uns
Impressum
Regeln
Suchen
Mitgliederliste
Membername:
Passwort:
Besucher:
4396328
Jetzt (Chat):
19 (0)
Mitglieder:
5239
Themen:
24223
Nachrichten:
234554
Neuestes Mitglied:
-insane-

ZFX
Coding-Foren
Sourcecode-Probleme
Tipps und Hinweise für sauberen Code
GepinntSeite: 1 2 3 4 >
AutorThema
Seraph Offline
Administrator


Registriert seit:
18.04.2002

England
Tipps und Hinweise für sauberen CodeNach oben.
Hi ho,

da der ursprüngliche Thread leider nicht mehr existiert, wollte ich ihn nochmal neu starten.

Es wäre schön wenn die etwas erfahrerenen Coder hier mal Tipps geben würden was sie unter einem sauberen und guten Code verstehen. Damit meine ich z.B. den Aufbau eines Projekts, die Struktur einer Klasse, etc. Nicht sehen möchte ich irgendwelche Dinge über Code-Optimierungen, spezielle Dinge zu irgendwelchen API's, etc.

Wie auch für den anderen Thread gilt auch für diesen daß ich doppelte Aussagen und "unsinnige" Diskussionen kommentarlos lösche. Es hilft keinem wenn sich niemand etwas von jemand anderen anhört und gleich dagegen sprechen muß.

Nach und nach werde ich mehrere solcher Threads starten in der Hoffnung daß die "Newbies" daraus lernen können. Die Punkte werden gesammelt und nach und nach in ein FAQ exportiert werden welches sich aber noch in Arbeit befindet.
05.12.2002, 01:59:47 Uhr
ZFX - 3D Entertainment
Kogs Offline
ZFX'ler


Registriert seit:
29.10.2002

Österreich
Re: Tipps und Hinweise für sauberen CodeNach oben.
Sauberes Programmieren fängt schon bei der Planung an.
Hier sollte man sich schon überlegen was ein Programm,
eine Klasse, eine Funktion, etc können soll und was
nicht.

Oft hat man einen Geistesblitz und coded diesen dann
mal schnell runter. Auch wenn dieser Teil jetzt
sauber programmiert wurde stellt man sehr oft fest,
eigentlich fehlt da noch das eine oder andere Feature,
und ab hier beginnt dann meistens das Hacken.

Jetzt müssen zB Datenstrukturen erweitert werden
um die neuen Funkionalitäten abbilden zu können.
Ab einem gewissen Punkt artet das dann in unsauberen
Code aus, in dem sich keiner mehr auskennt.

Das man von vornherein nicht alles planen kann ist
klar. Man wird jedoch feststellen, dass keine groben
Brocken mehr nachträglich eingefügt werden müssen.
Meist sind es Kleinigkeiten die man vorher nicht
bedacht hat.
GANZ WICHTIG: Diese 'Kleinigkeiten' nicht nur im
Code ändern sondern auch in der Planung/Design!!

Für Anfänger und manchmal auch für Fortgeschrittene
Programmierer ist die Planung oft schon eine
große Hürde. Die ersten Designs werden wahrscheinlich
nicht sonderlich toll sein und man wird oft auf die
Fresse fliegen, aber nur so lernt man besser zu werden.

Ich hoffe ich bin mit dem Artikel nicht zuweit vom
Thema entfernt

lg
Kogs
05.12.2002, 10:01:57 Uhr
Programmer Stefan Offline
ZFX'ler


Registriert seit:
13.08.2002

Schweiz
137756008
Re: Tipps und Hinweise für sauberen CodeNach oben.
Nach der Planung, kommt dann aber die Ausführung. Zum einen würde ich da verschiedene DLL's verwenden, zum Beispiel eine für Input, eine für Sound und eine für GUI oder so. Falls man will, kann man die Grafikengine auch in eine DLL packen, oder sonst hat man sie in der Game-Exe.

Ich persönlich bevorzuge Klassen. Eine für das Textur-Management, eine für Landschaft, eine für Objekte, eine für Potals etc.

Neben dem Coden gehört auch Ordnung auf der Festplatte. Am besten erstellt man sich für jeden DLL einen eigenen Ordner. Dort kommen dann die h und cpp Files und die Projektfiles.
Im Fall von VC++ verwendet man am besten auch einen einzelnen Arbeitsbereich für alle Projekte.
05.12.2002, 11:46:23 Uhr
Tell - Dawn of a Legend
Mastermind Offline
Knowledge-Admin


Registriert seit:
18.10.2002

Nordrhein-Westfalen
Re: Tipps und Hinweise für sauberen CodeNach oben.
Du versuchst es tatsächlich nochmal? Ich werd mal versuchen, dass zu Posten was ich letztesmal schon schrieb, einschließlich der Argumentation, die ich damal auf Anfrage dazuschreiben musste und Ergänzungen. Ich zwinge keinen sich dran zu halten, beatworte aber gerne Fragen. In der Hoffnung, dass es nicht wieder in der Katastrophe endet:

1) Halte dich an den C++ Standard!
a) Viele Posts handeln von Problemen mit char* char[] usw. Die meisten könnte man vermeiden wenn man std::string benutzt.
b) printf() und Konsorten sollten ebenfalls nicht verwendet werden. Streams sind typsicher und im Endeffekt praktischer. Man kann einen std::basic_ostream sehr leicht auf jedenb Abgeleiteten Stream umleiten und ist daher in der Ausgabe viel flexibler. Für eigene Datentypen definiert man einen operator << und die Ausgabe ist gegessen. Dass können printf() etc. nicht.
c) benutze new und delete bzw new[] und delete [] statt malloc() free() und realloc(). new ruft Konstruktoren auf, die Möglichkeiten Fehler zu machen sind eingeschränkt. Für Arrays deren Größe zur Kompiliereungszeit unbekannt ist, leistet ein std::vector bessere Dienste als jedes realloc()
d) benutze wann immer es möglich ist Standardcontainer und Standardalgorithmen. Ziehe sie eigenem Code vor. Du sparst wertvolle Programmier-Zeit. Die STL ist ein Kompromiß, aber sie ist der beste Kompromiß, den einige der besten Programmierer finden konnten. Wenn dir die MS Implementierung nicht passt, besorg dir 'ne andere, aber glaub (grade als Anfänger) nicht, du könntest die STL toppen.

2)
Zitat:
It's easier to optimize correct code than to correct optimized code

Das schrieb TGGC im erten Anlauf dieses Threads als Antwort auf abschweifender Erklärungen von mir zu diesem Thema. Glaubt es ihm. Kriegt euer Projekt zum Laufen und bugfrei, dann guckt, wo mehr Speed gebraucht wird und optimiert vorsichtig. Viele hier haben damals SPEED geschrien, ich schreie STIL. Es nutzt nichts aus jeder Zeile Code das Maximum rauszukitzeln um dann fewstzustellen, das der Bottleneck woanders liegt und der optimierte Code nicht mehr geht, und nicht mehr durchblickt werden kann.

3) Nimm Abstand von Makros und anderen hacks. Wenn sich etwas nicht in Standard C++ realisieren lässt, liegt das in schätzungsweise 99% der Fälle am Ansatz und nicht an C++. Einen guten Ansatz erkennst du IMHO daran, dass ein völlig unerwartetes Problem auftritt, sich dessen Lösung aber problemlos in den alten Ansatz integriert. Mit der Zeit entwickelt sich ein "Instinkt" für sowas.
Ich halte es immer für gut Sachverhalte und Zusammenhänge im Code "einfach nur" zu modellieren. Wenn etwas in der Realität in den Sachverhalt passt, wird es auch in den Code passen.

4) Schreib const korrekten Code. const ist dein Freund und spart dir viel Ärger. Der Aufrufer deiner Funktionen will wissen, was du mit seinen Referenzen machst. Ändert eine Methode eine Klasse nicht, sollte sie const sein. const kann in vielen Fällen #define ersetzen...

5) Lerne mit Namens- und Sichtbarkeitsbereichen umzugehen. Kaum etwas muss wirklich im globalen namespace stehen. Auch Klassen und Strukturen sind indirekt namespaces. Denke immer so, dass du nur soviel über den Rest des Codes weißt, wie deine Funktion. Dein Code wird so abstrakter. Code, der den Rest des Programmes kennen muss, hat meist ein Problem.

6) zum Schluss noch ein Paar Zitate ohne Anspruch auf Vollständigkeit:
Zitat:

"There is no silver Bullet"
"Es gibt kaum ein Problem, das man nicht durch eine zusätzliche Indirektion lösen kann"
"Premature optimization is the root of all evil"
"To iterate is human, to recurse devine"


EDIT:
7)Es scheinen im Moment void Pointer modern zu sein (Häufung von Posts zu diesem Thema). Type-Casts in jeder Form gehören zu IMHO den unsaubersten Sachen, die man im Code machen kann. Man verschenkt den Vorteil der strengen Typüberprüfung und erhöht das Risiko Fehler zu machen. Einige werden jetzt sagen: "hey ich bin Profi, ich weiß genau was ich tue!"
Na und? Auch Profis machen Fehler und es ist für den Compiler so leicht zu warnen, wenn man typsicher Programmiert. Es gibt vielleicht Situtionen, in denen man gar nicht ohne cast auskommt, mag sein, aber für die meisten Fälle ist man mit einer gemeinsamen Basisklasse oder einem Template besser bedient. Wenn man meint ein Cast zu brauchen, sollte man über einen alternativen Ansatz nachdenken.

Zitat:

Versucht nie schlau zu sein

05.12.2002, 15:46:54 Uhr
Work in Progress
Kogs Offline
ZFX'ler


Registriert seit:
29.10.2002

Österreich
Re: Tipps und Hinweise für sauberen CodeNach oben.
Sauberer Code:

Einige Tips von mir, wie ich mich in meinen Projekten auch
noch nach 2 Jahren auskenne

- Jede Klasse in ein eigenes File (Filename = Klassenname)

- Inline Funktionen NICHT in das Header File schreiben
stattdessen im CPP File mit dem Schlüsselwort 'inline' definieren

- Prinzipiell in Header Files keinen Code schreiben
Hier gehören nur Klassendefinitionen, Funktionsköpfe, strukturen etc.

- vermeiden von aa?xx:yy Ausdrücken da sie wenn komplex geschrieben,
schwer zu lesen sind

- Kommentiere nicht zuwenig aber auch nicht zuviel. Was auf keine
Fall fehlen sollte:
Verwendungszweck von Globalen Variablen wenn unbedingt nötig
Verwendungszweck von Klassen
Verwendungszweck von Funktionen/Methoden
Beschreibe wofür welche Parameter von Funktionen gut sind

- Verwende enums richtig:
Code:
typedef enum {
    enum_1,
    enum_2,
    enum_3
} testenum;

// FALSCH:    
int iTest = enum_1;
// oder
int iTest = (testenum)enum_1;

// RICHTIG:
testenum eTest = enum_1;

eTest = 1;    // compiler läßt das nicht zu

Funktionieren tut beides, bei der richtigen Version prüft jedoch
schon der Compiler, ob dem enum was richtiges zugewiesen wird.

Ich sehe diese 'int' Version sehr oft, weiß nicht warum so viele Leute
das so machen, macht in der Regel nur Ärger.

- verwende in 'if' abfragen keine Funktionen
Code:
// statt
if (x == getSomething())
// schreibe
y = getSomething();
if (x == y)


Grund: Tut das Programm mal nicht so wie es sollte, dann muß man
debuggen. Schreibt man die Funktion in das 'if', sieht man den
Rückgabewert im debugger nicht

- Überprüfe stets Funktionsparameter auf ihre Gültigkeit
Zumindest bei Pointer, die in eine Funktion übergeben werden,
soll innerhalb der Funktion gleich am Anfang eine Abfrage auf
NULL sein, außer NULL werte sind erwünscht.
Code:
void Test(int *piTest)
{
    if (NULL == piTest)
        return;
    *piTest = 17;
}

Ohne diese Abfrage würde das Beispiel, wenn ich NULL übergebe
abstürzen.
Wichtig: Glaube nicht, das immer alle Personen die deine Funktionen
verwenden, diese richtig verwenden. Schließe Fehler aus.

- Seit C++ verwende Referenzen statt Pointer
Das beispiel aus dem vorigem Punkt sieht dann so aus
Code:
void Test(int &iTest)
        {
            iTest = 17;
        }

Das ist einfacher zu lesen, und es schleichen sich weniger
Fehler ein.

- drehe 'if' abfragen mit konstanten um
bsp:
Code:
// statt
if (x == 1) ...
// schreibe
if (1 == x) ...

Grund: Man kann keine Zuweisungsfehler mehr machen. Vergisst man
ein '=' also macht versehentlich eine Zuweisung statt einem Vergleich,
findet das schon der Compiler.
Code:
if (x = 1)    // Ist zulässig, liefert jedoch falsches ergebnis
if (1 = x)    // compiler schreit da zuweisung auf konstanten verboten ist


Diese schreibweise ist allerdings sehr gewöhnungsbedürftig.
Sollte man das Verwenden, dann aber durchgängig, nicht einmal so und
beim anderen mal wieder anders, wo ich auch schon beim nächsten Punkt bin.

- Behalte (d)einen Stil in allen Sourcen bei
schreibe nicht ein mal die { am ende von einer zeile und ein anders
mal in die nächste zeile etc.

- Ziehe eine einheitliche Notation durch
schreibe als Prefix für Variablen zB einen 'int' nicht ein mal 'iVariable'
und ein anderes mal ein 'nVariable' oder was auch immer

- Benenne Funktionen und Variablen nach ihrem Verwendungszweck

- Behalte Funktionen/Methoden übersichtlich
Als Faustregel gilt, eine Funktion sollte auf eine Bildschirmseite passen.
Das ist natürlich nicht immer möglich, aber bei funktionen ab 500 Zeilen
aufwärts sollte man sich überlegen ob es nicht möglich ist, funktionalität
in neuen Funktionen auszulagern.

- Rücke bei einem Neuen Codeblock immer schön brav ein
Als Tabweite haben sich bei mir 4 Zeichen als optimal erwiesen.
Schreibe nicht ein mal einen Tab zum einrücken, ein anderes mal
4 Leerzeichen.

- Die Reihenfolge der Funktionen in den C Files sollte identisch mit der
in den H Files sein.


So, mehr fällt mir im Moment nicht ein (ups ist doch recht lang geworden )
Ich habe mit absicht Sachen die in vorhergehenden Artikeln schon erwähnt
wurden hier nicht noch einmal gepostet (zB STL, const)

Mit diesen Tips möchte ich keinen zwingen, ab jetzt genau so zu
programmieren, das sind lediglich Vorschläge mit denen ich recht gut
zurecht komme.

Für konstruktive Kritik zu diesen Punkten bin ich offen, es gibt sicher den einen oder
anderen besseren Weg.

lg
Kogs
06.12.2002, 11:46:03 Uhr
Atem Offline
ZFX'ler


Registriert seit:
04.12.2002

Niedersachsen
Re: Tipps und Hinweise für sauberen CodeNach oben.
Wie ich mein Code sauber mache und nicht sauber plane:

Schreibe einfach drauf los, wenn du eine Idee hast. Das Ziel ist es, die Idee zu realisieren.
Es gibt einen Moment, an dem dir alles viel zu kompliziert erscheint. Dann fange an umzuschreiben. An diesem Punkt ist Planung erfolgreich, denn du hast jetzt ein Gefühl dafür, warum etwas kompliziert ist. Hast du einmal umgeschrieben, so wird dein Code ganz von selbst sauber, weil es ja einfacher geworden ist.
Erst jetzt darfst du einen Schritt weitergehen und neue Funktionalität einbauen. Mit der Zeit beginnt dein Code wieder komplizierter zu werden. Nun kommt wieder die Phase, indem du einiges Umschreibst und vereinfachst. Dadurch wird der Code wieder sauberer und passt sich den Anforderungen an.

Höre auf dich zu ärgern, daß dein Code zu unsauber wird. Das liegt einfach in der Sache selbst. Je umfangreicher dein Code wird, desto mehr gibt es Verbindungen untereinander und desto mehr Verbindungen es gibt, um so schlechter läßt sich etwas Planen. Das bedeutet, daß du von Anfang garnicht planen kannst. Denn du hast im Grunde noch keine Erfahrung mit dem System. Erst dadurch, daß du einfach drauflosschreibst, gewinnst du die nötige Erfahrung um etwas zu vereinfachen und zu säubern. Der Code meldet sich quasie selbst und sagt dir "Schau, ich bin jetzt viel zu kompliziert und mache dir echt Schwierigkeiten und möchte garnichts neues mehr auf mir Tragen, wenn du mich nicht zuerst säuberst und vereinfachst".

Der ganze Trick dabei besteht einfach in diesem Wechselspiel zwischen Schreiben und Verbessern.
Etwas wird nicht von Anfang an gut. Erst durch die Iteration, die du mit dem Verbessern ausführst, gewinnt es an Qualität.

Ich kenne niemanden, der sich einfach an ein Programm setzt und sofort sauberen Code erzeugt. Gelingt das mit den Anfängen, so liegt das nur daran, daß der jenige soetwas schon vorher programmiert hat.
Ich erlebe es immer wieder, daß die Anfänge, die ich schon oft programmieren mußte, jedesmal besser aussehen, wenn ich sie neu schreibe. Das heißt aber nicht, daß die neuen Sachen, die ich dazu schreibe, automatisch besser werden, weil ich es gelernt habe sauberen Code zu erzeugen. Im Gegenteil, sie verkomplizieren das ganze System und sagen mit dadurch "Hey, es ist Zeit umzuschreiben und zu säubern". Und dann fange ich an den alten Code umzuschreiben und wenn ich das nächste mal das gleiche schreibe, so wird alles sauberer. Warum wohl ?

There is no silverbullet. Warum also die ganzen Methoden um ein sauberes System zu programmieren ?
Diese Methoden haben schon einen Sinn, aber erst
dann wenn du siehst wo du es einsetzen kann.
Bevor dein Code nicht sagt "Ich bin unsauber", kannst du vorher nicht wissen wo du etwas einsetzt. Es ist klever sich Methoden durchzulesen. Denn an dem Punkt, wo du etwas verbessen mußt, wirst du ganz von selbst die neuen Sachen ausprobieren, weil die Neugier eben groß ist.
Dein Code hat seinen eigenen Geschmack. Einige Methoden mag es und andere eben nicht. Falls du eine Methode ausprobierst und es wird dadurch komplizierter, so hat dein Code eben einen anderen Geschmack. Probiere es mit anderen Dingen.

Wenn du das lange genug gemacht hast, so kennst du viele Bausteine, die du schon vorher in einem System verwendet hast. Du weiß dann, daß diese Bausteine gut zusammenarbeiten und sich mögen. Das ist nach meiner Meinung ein sauberer Code. Die Bausteine arbeiten in Harmonie miteinander zusammen.
Dann nimmst du einen weiteren Baustein mit in dein System dazu. Nun streiten sie sich ein wenig miteinander. Deswegen arbeitest du an den Verbindungen, damit sie harmonischer zusammenarbeiten.
Mit der Zeit erlernst du ein sauberes System zu programmieren.
Es gibt dort nämlich einen Unterschied zwischen sauberen Code und sauberem System.

Eine Klasse, die für dich total sauber erscheint, setzt du in einem System ein. Sie verträgt sich nicht mit dem System und um die Verbindungen zu realisieren, fängst du an Dinge in die Klasse zu schreiben. Dadurch wird sie unsauber. Hättest du die selbe Klasse in einem anderen System verwendet, dann könnte es wirklich sein, daß die Klasse dort sauber aussieht, weil sie sich mit dem System verträgt.

Deswegen gibt so etwas wie sauberen Code eigentlich garnicht. Sie steht immer in Kontext
des System in dem sie verwendet wird.
Eine Klasse, die sich mit vielen Systemen verträgt würde ich sauberer nennen als eine Klasse, die sich nur mit wenigen Systemen verträgt. Willst du eine solche Klasse verbessern um sie verträglicher mit vielen Systemen zu machen, so wird dies nur mit mehr Code möglich sein. Dann greift aber das hinein, was ich ganz am Anfang geschrieben habe. Du fängst an zu Erweitern und zu Verbessern. Aber genau das ist ja die Methode um etwas sauber zu machen. So wird die Klasse eben immer mehr Systemsauberer und fängt mit der Zeit an selbst zu einem kleinen System zu werden. Sie wächst, aber sie wächst nur dann, wenn sie sauber ist. Hört sie auf zu wachsen und wehrt sich dagegen, so will sie die Pempars gewechselt haben

Die Dinge, die ich schreibe sind keine Methoden
wie "Mach dies und mache nie das".
Aber ich hoffe, daß sie ein Gefühl dafür geben, wie man sauberen Code erzeugt. Es ist der Dialog, den du mit deinem Programm führst.
Jemand, der diesen Dialog nicht führt, wird immer mehr in sein System hineinschreiben. Der alte Code wird immer mehr schreien und nach Hilfe rufen. Wenn du nicht darauf hörst, so bleibt sie eben unsauber und "kackt" in das ganze System hinein . Die neuen Bausteine freunden sich mit dem unsauberem Code an. Und weil sie versuchen mit der Unsauberkeit zu kommunizieren, müssen sie sich selbst der unsauberen Schnittstelle anpassen.
Wenn du die ganze Zeit nicht auf das hörst, was dir dein Code sagt, so hast du am Ende das Gefühl "alles beschissen". Naja, im Grunde stimmt dein Gefühl ja auch

Entschuldigt bitte meine Fäkalworte. Sie haben so gut in das System gepasst.

Viele Grüße,

Atem
06.12.2002, 20:08:53 Uhr
random task Offline
ZFX'ler


Registriert seit:
10.12.2002

Hessen
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zu gutem Stil gehört gerade in Anwendungen, bei denen sich die Back-End-Technik rasant ändert (DirectX 9...tausend) auch Plattformunabhängigkeit bzw. Portabilität. Man sollte also sämtlichen Code, der irgendwelche fremden APIs benutzt, in Klassen kapseln.

Beispiel: Man möchte mit seiner 3D-Engine sowohl DirectX als auch OpenGL unterstützen. Man benötigt also ein Interface, das von diesen APIs abstrahiert:
Code:
class Display {
public:
  //! Initialisierung
  virtual void create(const DisplayMode &) = 0;

  //! Shutdown
  virtual void destroy() = 0;

  //! Framebuffer löschen
  virtual void clear(int flags, const Color &) = 0;

  //! Framebuffer updaten
  virtual void update() = 0;

  //! Textur erstellen
  virtual Surface * createTexture(const std::string & name, int flags = 0) = 0;

  //! Shader erstellen
  virtual Shader * createShader() = 0;

  //! Vertexbuffer erstellen
  virtual VertexBuffer * createVertexBuffer() = 0;

  //! Indexbuffer erstellen
  virtual IndexBuffer * createIndexBuffer() = 0;

  //! Caps zurückgeben
  virtual long capabilities() = 0;

  //! Wird vorm rendern aufgerufen
  virtual void beginScene() = 0;

  //! Wird nach dem rendern aufgerufen
  virtual void endScene() = 0;

  //! Lichtquelle setzen
  virtual void setLight(int num, Light *) = 0;

  //! Transformationsmatrix setzen
  virtual void setMatrix(MatrixType, const Matrix &) = 0;

...
};


Die rein virtuellen Methoden dieser Klasse muss man jetzt für jede API implementieren, ebenso die Klassen Surface, VertexBuffer, IndexBuffer und Shader. Die Client-Anwendung erstellt dann am Anfang ein entsprechendes Objekt:

Code:
Display * display = new OpenGLDisplay();


...und der restliche Code ruft die virtuellen Methoden dieses Objekts auf, die dann die entsprechenden API-Funktionen benutzen, oder bezieht über die create...()-Methoden die passenden Objekte, die wiederum über virtuelle Methoden verfügen.
Auch für die Audio-API, Input-API, Netzwerk-API, usw. eignet sich dieses System. Falls man vor hat, sein Game irgendwann mal auf Linux zu portieren (oder auf Windows ), muss man "lediglich" ein paar neue Klassen schreiben, die die verfügbaren System-APIs ansteuern, fertig.
Für kleinere Tech-Demos ist so ein System sicherlich Overkill, aber wer seine Engine mehr als einmal verwenden möchte, sollte sich vorher Gedanken machen, ob er sich auf eine API festlegt, oder lieber alle Wege offenhält.
Ein Nachteil ist natürlich, dass man nicht eben mal ein neues Feature reinhacken kann, da sämtliche Funktionalität ja nur über die Interface-Klassen zur Verfügung steht. Aber durch das Prinzip der getrennten Klassen (Display, Shader, ...) hat man hier doch einiges an Freiheit. Man kann z.B. ein neues DirectX-Renderstate in der Shader-Klasse definieren, was dann halt erstmal nur in der DirectXShader-Klasse unterstützt wird, zum Testen reicht das allemal.
Performance-mäßig ergeben sich gerade durch die Implementierung via Vertexbuffer keinerlei Unterschiede. Die DirectX-Version benutzt d3dd->DrawPrimitive(...), die OpenGL-Variante glDrawElements(...). Der einzige Overhead ist ein virtueller Funktionsaufruf pro Vertexbuffer.
Das Shader-Konzept kann sogar Geschwindigkeitsvorteile bringen, da man Renderstates intern cachen, und so unnötige API-Aufrufe (teuer: SetRenderState) vermeiden kann.
Ein solches System ermöglicht einem schlußendlich auch eine flexible Plug-In-Architektur, da man ja nur die Interface-Deklarationen benötigt, um mit der Engine zu arbeiten. Man kann also sämtliche System-abhängigen Klassen in DLLs kompilieren, die man bei Bedarf austauschen (updaten) kann. Übrig bleibt dann nur ein Platform-unabhängiger Engine-Kernel, der sauber und frei ist von irgendwelchen unsäglichen API-Konventionen ála COM .

"Nanu, wofür eigentlich die Ausrufezeichen in den Comments?"
Jedes größere Projekt sollte über eine Dokumentation der API verfügen. Leider sind externe Docs immer schnell veraltet oder werden aus Zeitgründen erstmal weggelassen. Die beste Dokumentation befindet sich daher im Quellcode selbst, in Kommentaren.
Das wunderbare Tool Doxygen kann automatisch aus Quellcode eine HTML-Dokumentation generieren. Dazu muss nur jeder Kommentar, der z.B. den Zweck einer Methode beschreibt, über deren Deklaration stehen und diese //!-Syntax benutzen. Wenn man sich daran gewöhnt hat, ist es ganz normal, und das Schreiben, Formatieren und Verlinken einer Dokumentation kann man sich sparen...
12.12.2002, 07:47:08 Uhr
Yannick Offline
ZFX'ler


Registriert seit:
29.06.2002

Schweiz
Re: Tipps und Hinweise für sauberen CodeNach oben.
Benutz die Konstuktoren und Destruktoren.

Hier eine kleine Kritik: Im buch 2 von Stefan Zerbst hat er im Partikelsystem eigene Funktionen für das Initialisieren und freigeben von dynamischem Speicher gemacht. Das hätte er bequem in einem Konstruktor beispielsweise Destruktor machen können.
03.05.2003, 22:00:21 Uhr
Eisflamme Offline
ZFX'ler


Registriert seit:
26.05.2002

Nordrhein-Westfalen
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zitat:
Das hätte er bequem in einem Konstruktor beispielsweise Destruktor machen können.

Hört sich an, als wäre ein Destruktor eine spezielle Art von Konstruktor.

Ich habe mir eure Tipps durchgelesen und finde sie absolut einleuchtend und wirkungsvoll.
Das Thema sollte auf alle Fälle in die FAQ verschoben werden, denn dort kann auch weitergeschrieben werden.

MfG MAV
03.05.2003, 23:12:57 Uhr
Mihahome Revolution 2.0
Cygon Offline
Programmier-Berater


Registriert seit:
18.08.2002

Hessen
4389513
Re: Tipps und Hinweise für sauberen CodeNach oben.
Sporadisch auftretende und kaum nachvollziehbare Fehler sind die Lieblinge eines jeden Entwicklers. Programmierung sollte nach Möglichkeit Deterministisch bleiben, d.h. das Programm sollte nicht meistens laufen oder ein Weilchen stabil bleiben, sondern durchweg zuverlässig sein.
Hier ein paar Tipps mit denen man dafür sorgen kann (unabhängig von jeder Programmiertechnik!)


Sorge für Konsistenz (Durchgängigkeit) in deinem Code
Versuche, eine gewisse Standardisierung innerhalb eines Projektes durchzuführen. Das betrifft nicht nur gleiche Einrückung, Benennung und Kommentartechnik, sondern auch das Design und die Art, mit der man Probleme löst. Wenn die einzelnen Komponenten nicht aneinander passen, gibt es schon im eigenen Projekt "Flickstellen", die das Verständnis erschweren und Fehler provozieren.

Beispiel: Du arbeitest an einem einfachen Spiel, das du sauber mit dem neuesten Stand deiner Kenntnisse implementierst. Hier im Board liest du dann über Exceptions und möchtest sie auch in deinem Spiel nutzen. Du schreibst also eine DrawPixel()-Funktion, die bei einem nicht unterstützten Farbformat eine BadPixelFormatException wirft. Wenn DrawPixel() von altem Code zwischen Lock() und Unlock() für eine Surface aufgerufen wird, haut die Exception durch die Funktion durch. Zwar wird sie letztendlich gefangen, aber die Surface ist dann immernoch gelockt. Sobald du die Surface zeichnen willst, hast du einen unerklärlichen D3DERR_SURFACEBUSY.


Prüfe alle Rückgabewerte richtig
Wenn du mit einer API arbeitest, die Rückgabewerten oder Exceptions verwendet, dann mach dich mit deren Schema vertraut. Sonst werden Fehler erkannt die keine sind oder echte Fehler ignoriert. Es ist immer eine gute Idee, sich eine inline-Funktion zum umwandeln von Rückgabewerten/Exceptions einer Fremd-API zu schreiben.

Beispiel: Viele DirectX-Funktionen geben im Normalfalle S_OK zurück, und falls sie nichts zu tun hatten S_FALSE. Wenn man nur auf S_OK abfragt, läuft das Programm normal, bis z.B. mal eine USB-Maus angeschlossen wird (anderes Poll()), oder eine ATI-Grafikkarte benutzt wird, deren neuer Treiber redundante RenderStates mit S_FALSE quittiert.


Verstecke Details und nutze Transparenz
Versuche unnötige Implementierungsdetails nicht nach aussen bekannt zu machen. Das füllt die Schnittstellen mit unübersichtlichem Ballast und man schreibt gewissen Arbeitsschritte wieder und wieder. Transparenz bedeutet, dass etwas durchsichtig, also unsichtbar für den Verwender ist. Dinge, die der Verwender nicht anfasst, kann er nicht kaputt machen.

Beispiel: Deine Texturen werden in einem TextureManager verwaltet. Die Texturen werden vom ImageLoader geladen und vom RenderDevice erstellt. Wann immer du eine Textur brauchst, guckst du im TexturManager nach, ob sie schon geladen wurde, andernfalls erstellst du eine neue, lädst ein Bild hinein und fügst sie dem TextureManager hinzu. Jeder, der eine Textur verwenden möchte, muss über die drei Systeme bescheid wissen und das Vorgehen kennen. Eine GetTexture()-Methode im TextureManager dagegen wäre sofort verständlich. Dem Verwender genügt es, dass GetTexture("xyz.bmp") ihm die entsprechende Textur gibt, egal ob gecacht oder neu.


Lasse Funktionen nicht "still" versagen
Wenn ein Fehler auftritt, dann solltest du dem Aufrufer der Funktion ein Ultimatum stellen: Entweder der Fehler wird behandelt, oder das Programm bricht ab. Andernfalls kann das Programm langsam in einen undefinierten Zustand übergleiten.

Beispiel: Du schreibst eine Linked List. Beim einfügen eines Elements tritt ein Fehler auf. Statt die verbogenen Listenzeiger wiederherzustellen und den Fehler zu melden, gibst du einfach NULL zurück. Das Programm läuft weiter, aber ein Listenzeiger zeigt ins Leere. Irgendwann später sürzt das Programm ab.


Schreibe kleine und sichere Einheiten
Schreibe kleinere Einheiten, die sicher wieder verlassen werden können. Wenn ein Lock() erfolgreich aufgerufen wird, sollte die Funktion garantieren können, dass Unlock() aufgerufen wird. Eine Klasse sollte in jedem Zustand sicher mit dem Destruktor abgebaut werden können.

Beispiel: Du schreibst eine CGrafik-Klasse. Da die Klasse nicht nur genau eine Zweck erfüllt, gibt es eine Vielzahl von Verknüpfungen zu anderem Code. Quasi jede Quelldatei in deinem Programm greift auf diese Klasse zu und du verlierst den Überblick über die Bedingungen, unter denen die Klasse abgebaut werden kann. Nachdem ein Fehler das Programmende einleitet und die Klasse freigegeben wurde, tritt beim weiteren Abbauen ein Schutzverletzung auf, weil irgendwo versucht wurde, auf die Klasse zuzugreifen. Der tatsächliche Fehler gelangt nie zur Meldung und du jagst einen Folgefehler.


-Markus-

P.S. Bitte um 2 Uhr Nachts nicht zu viel von Rechtschreibung und Satzbau erwarten ^_^
04.05.2003, 01:33:43 Uhr
LunaticSystems
Seraph Offline
Administrator


Registriert seit:
18.04.2002

England
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zitat:
Benutz die Konstuktoren und Destruktoren.

Hier eine kleine Kritik: Im buch 2 von Stefan Zerbst hat er im Partikelsystem eigene Funktionen für das Initialisieren und freigeben von dynamischem Speicher gemacht. Das hätte er bequem in einem Konstruktor beispielsweise Destruktor machen können.

Ich kenne zwar das Partikelsystem aus Buch 2 nicht, aber eigene Init- und CleanUp-Funktionen haben durch aus ihren Vorteil, da man so nicht die Klasseninstanz zerstören und wieder neu erstellen (Gilt besonders wenn man Teile ohne Änderung wiederverwenden kann.), sondern lediglich die entsprechenden Funktionen aufrufen muß. Zudem braucht man die beiden Funktionen nur in den Constructor/Destructor aufzurufen.

Das gilt natürlich nicht für alle Fälle, ich wollte damit nur sagen, daß es durchaus seine Vorteile haben kann eigene Funktionen zu benutzen.
04.05.2003, 02:23:31 Uhr
ZFX - 3D Entertainment
thomy Offline
ZFX'ler


Registriert seit:
16.01.2003

Baden-Württemberg
Re: Tipps und Hinweise für sauberen CodeNach oben.
Man sollte immer Destruktoren / Konstruktoren verwenden, da diese Automatische aufgerufen werden. Ich mache folgendes, um die Klasse später neu Initialisieren zu können:

Code:
class C
{
public:
    C()
    { 
        Initialize();
    }
    ~C()
    {
        Destroy();
    }

    Destroy();
    Initialize();
[...]
};

[...]
C Instance;  // Instanz wird automatisch
             // initialisiert und später
             // wieder freigegeben.

C.Destroy();    // Bei Bedraf Klasse neu
C.Initialize(); // initialisieren
04.05.2003, 12:38:11 Uhr
ChrisM Offline
ZFX'ler


Registriert seit:
16.03.2002

Rheinland Pfalz
65348179
Re: Tipps und Hinweise für sauberen CodeNach oben.
So und was machst du, wenn ein Benutzer nur Destroy() ohne Init() danach oder nur Init() aufruft. Im zweiten Fall dürftest du wohl einige Speicherfreigaben vergessen und im ersten Fall das komplette Objekt ungültig machen.
Das Ziel (zumindest mein Ziel) ist ja grad, dass das Objekt die ganze Zeit zwischen ctor und dctor gültig ist.

Dann doch lieber das Objekt löschen und ein neues erstellen.

ChrisM
04.05.2003, 12:41:08 Uhr
ChrisMs Baustelle
ChrisM Offline
ZFX'ler


Registriert seit:
16.03.2002

Rheinland Pfalz
65348179
Re: Tipps und Hinweise für sauberen CodeNach oben.
@Cygon: Respekt, so viel zu tippen (ich schließ mich allen Punkten an)!

Achja, für's Locken kann man bequem eine Lockklasse machen, die im Destruktor unlockt und im ctor lockt, so ist nur im aktuellen Scope gelockt und wenn der verlassen wird, wird automatisch geunlockt (auch beim Werfen von Exceptions -> exceptionsicheres Programmieren).

ChrisM
04.05.2003, 12:44:16 Uhr
ChrisMs Baustelle
Cygon Offline
Programmier-Berater


Registriert seit:
18.08.2002

Hessen
4389513
Re: Tipps und Hinweise für sauberen CodeNach oben.
Genau, man sollte dem Benutzer mit dem Entwurf bereits von möglichst vielen Dummheiten abhalten. Wenn man den Code nicht vervielfältigen möchte, kann man das wie folgt lösen:
Code:
class Irgendwas {
  public:
    Irgendwas() { Init(); }
    ~Irgendwas() { Shutdown(); }

    void Reset() { Shutdown(); Init(); }

  private:
    void Init() { /* ... */ }
    void Shutdown() { /* ... */ }
};

Wobei man dann natürlich immernoch in Bedrängnis kommt, wenn in Init() ein Fehler auftritt, denn dann ist der alte Zustand bereits verloren, die Klasse bleibt in einem ungültigen Zustand, und der Fehler darf nicht behandelt werden.
Bei der transaktionale Programmierung dreht sich alles um die schöne Regel (noch'n Tipp!): "Wenn eine Methode fehlschlägt, dann ist es so, als sei die Methode niemals aufgerufen worden".

@ChrisM: Zustimmung. Verraten wir den anderen doch auch noch den Namen dieser Technik: RAII - Resource Acquisition Is Initialization. Ist natürlich auch bei jedem anderen Funktionspaar zu gebrauchen, wie BeginScene()/EndScene(), ausserdem wesentlich performanter als ein try..catch()-Block

-Markus-
04.05.2003, 13:00:44 Uhr
LunaticSystems
Thesbu Offline
ZFX'ler


Registriert seit:
25.05.2003

Bayern
Re: Tipps und Hinweise für sauberen CodeNach oben.
Ich halte nichts davon extra Init- und Shutdownmethoden zu definieren, hierzu gibts es nun mal die Konstruktoren und die Destruktoren die ihre Arbeit ja sogar automatisch verrichten.

Wenn man Teile eines Objektes einer Klasse während des Betriebs freigeben muss und andere Teile der selben Klasse nicht ist es im allgemeinen sinnvoller die Klasse in mehrere Klassen aufzuteilen.

Anstelle eines Resets ist es im allgemeinen auch sauberer einfach ein neues Objekt zu erstellen und das alte zu löschen.
25.05.2003, 23:44:29 Uhr
Cygon Offline
Programmier-Berater


Registriert seit:
18.08.2002

Hessen
4389513
Re: Tipps und Hinweise für sauberen CodeNach oben.
Eben. Denn der Besitzer des Objektes möchte warscheinlich auch Erfahren, dass das Objekt resetted werden soll

Wenn man der entsprechenden Variable also eine neue Instanz zuweisen muss, dann muss man dazu auch Zugriff auf diese Variable haben, sprich, entweder der Besitzer sein oder Zugang zu den Interna des Besitzers (z.B. durch gewrappte Reset()-Methode) haben.

-Markus-
26.05.2003, 12:48:02 Uhr
LunaticSystems
scones Offline
ZFX'ler


Registriert seit:
11.05.2003

Berlin
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zitat:

Ich kenne niemanden, der sich einfach an ein Programm setzt und sofort sauberen Code erzeugt.


Guten Tag.
Ich bin scones.

Deine Planungsarbeit kann man nicht als solche bezeichnen.
Deine Argumentation deutet darauf hin, dass du nicht planst.

Eine gute Planung beginnt auf dem papier ( oder notepad oder umleditor je nach vorlieben).

wenn du dein klassendesign dort erfolgreich zusammengestellt hast, kannst du damit beginnen den Computer einzuschalten (vorrausgesetzt die Planung wurde nicht mit notepad oder UML-Editor umgesetzt).

Du solltest jetzt nur das schreiben, was geplant war.
Wenn du damit fertig bist, hast du viele Punkte entdeckt, die dir verbesserungswürdig oder ausbaufähig erscheinen.
Wunderbar. Jetzt kannst du den Compiler wieder ausstellen und wieder in die Planungsphase übergehen, da du in deinem Klassendesign die neuen Klassen/Memberfunktionen nachtragten kannst und etwaige auswirkungen der Modifikationen auf das System überprüfen darfst.

Das Ganze nennt sich System Engineering.
Das hier geschilderte stellt allerdings nur einen Bruchteil davon dar.
Da draußen in der welt gibt es viele schlaue Menschen, die Bücher dazu verfasst haben.

Ganz nebenbei ist das eine Standardvorraussetziung bei Firmen, wenn du jemals leitender Angestellter sein willst.

PS: was ich bisher in diesem Thread vermisst habe:
Kommentiert Euren Quellcode !

sapere aude!
27.06.2003, 15:37:37 Uhr
asm68k.org
Kimmi Offline
ZFX'ler


Registriert seit:
10.10.2002

Schleswig-Holstein
93425079
Re: Tipps und Hinweise für sauberen CodeNach oben.
Da ich gerade berufstechnisch mit unsauberen Code arbeiten muss, noch einige Nachträge:

1.) Eigene Datentypen immer in Klassen (bei OOP) beziehungsweise durch Funktionen (wenn nicht OOP, gibt es noch aus Performancegründen, wie ich feststellen musste) kapseln, so dass kein direkter Zugriff auf die Daten notwendig wird. Das macht das ganze System wesentlich einfacher zu warten beziehungsweise zu erweitern. Wenn diese Kapselung oder auch Abstraktionsebene steht, dokumentier!!! sie so, dass ein Fremder sie irgendwann später noch benutzen kann. Man vergisst schneller, was für Geistesblitze man dort hatte, als man glaubt. Und neu hinzugekommene Entwickler wollen halt dran arbeiten, nicht rätseln, was das heissen soll.

2.) Vermeide feste Datenstrukturen, wie z.b.

Code:
typedef struct {
    floar dX;
    float dY;
} MyPoint;


,um das Datenmodell zu verwalten. Templates bzw. dynamische Datenarchitekturen (so ala 2 * float bitte) wirken Wunder! Und ganz wichtig: Auch die Schnittstellen bzw. die Implementierung dieser dokumentieren.

3.) Vermeide coole Hacks, ausser dir versteht sie eh keiner (gehe zumindest davon aus).

4.) Kompliziert aussehende Lösungen sollten rel. einfach und moduliert umgesetzt werden (so bleibt man flexibel und muss nicht aller 10 Mal machen).

Das sind Dinge, die nachfolgenden Generationen das Leben wesentlich vereinfachen. Hoffe, war nicht zuviel Repost.

MfG Kimmi
27.06.2003, 19:17:51 Uhr
Kurzer Weblog
[VoD]Sebastinas Offline
ZFX'ler


Registriert seit:
28.07.2003

Österreich
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zitat:
Beispiel: Man möchte mit seiner 3D-Engine sowohl DirectX als auch OpenGL unterstützen. Man benötigt also ein Interface, das von diesen APIs abstrahiert:
Code:
class Display {
public:
  //! Initialisierung
  virtual void create(const DisplayMode &) = 0;

  //! Shutdown
  virtual void destroy() = 0;

  //! Framebuffer löschen
  virtual void clear(int flags, const Color &) = 0;

  //! Framebuffer updaten
  virtual void update() = 0;

  //! Textur erstellen
  virtual Surface * createTexture(const std::string & name, int flags = 0) = 0;

  //! Shader erstellen
  virtual Shader * createShader() = 0;

  //! Vertexbuffer erstellen
  virtual VertexBuffer * createVertexBuffer() = 0;

  //! Indexbuffer erstellen
  virtual IndexBuffer * createIndexBuffer() = 0;

  //! Caps zurückgeben
  virtual long capabilities() = 0;

  //! Wird vorm rendern aufgerufen
  virtual void beginScene() = 0;

  //! Wird nach dem rendern aufgerufen
  virtual void endScene() = 0;

  //! Lichtquelle setzen
  virtual void setLight(int num, Light *) = 0;

  //! Transformationsmatrix setzen
  virtual void setMatrix(MatrixType, const Matrix &) = 0;

...
};


...

Code:
Display * display = new OpenGLDisplay();



Du hast nur ein Problem bei diesem Code!
Wenn du jetzt delete für display aufrufst, führt das zu undefinierten Verhalten, da du keinen virtuellen Destruktor definiert hast.
Die meisten Compiler, die ich kenne, rufen hier nur den Destruktor der Basis-Klasse auf, aber nicht den Destruktor der abgeleiteten Klasse. Verherende Ergebnisse gibt es dann, wenn in der abgeleitetende Klasse mit dynamischen Speicher gearbeitet wird.

MfG
28.07.2003, 18:07:01 Uhr
norebo Offline
ZFX'ler


Registriert seit:
26.02.2002

Deutschland
98138075
Re: Tipps und Hinweise für sauberen CodeNach oben.
Zitat:
Sauberer Code:

- verwende in 'if' abfragen keine Funktionen
Code:
// statt
if (x == getSomething())
// schreibe
y = getSomething();
if (x == y)


Grund: Tut das Programm mal nicht so wie es sollte, dann muß man
debuggen. Schreibt man die Funktion in das 'if', sieht man den
Rückgabewert im debugger nicht



zumindest der vc++ debugger gibt dir immer den rückgabewert einer funktion aus! also meiner meinung nach kann man ruhig funktionen in if abfragen verwenden
28.07.2003, 19:28:26 Uhr
Eisflamme Offline
ZFX'ler


Registriert seit:
26.05.2002

Nordrhein-Westfalen
Re: Tipps und Hinweise für sauberen CodeNach oben.
Eben.
Wenn es größere Funktionsblöcke ineiander verschatelt gibt, kann man das schön in einer Zeile schreiben und muss es nicht mit etlichen temporären Variablen besorgen.
28.07.2003, 19:33:16 Uhr
Mihahome Revolution 2.0
ChrisM Offline
ZFX'ler


Registriert seit:
16.03.2002

Rheinland Pfalz
65348179
Re: Tipps und Hinweise für sauberen CodeNach oben.
Richtig, das ist nicht nur schneller und kürzer, sondern hält sich auch an den Grundsatz:
Mache niemals Code von deiner IDE abhängig!

ChrisM
28.07.2003, 20:23:29 Uhr
ChrisMs Baustelle
Lord Delvin Offline
ZFX'ler


Registriert seit:
05.07.2003

Baden-Württemberg
166781460
Re: Tipps und Hinweise für sauberen CodeNach oben.
Und noch was: Wenn du schon meim programmieren Musik hören musst, dann höre ruhige Musik ohne Gesang
04.08.2003, 13:39:56 Uhr
Cygon Offline
Programmier-Berater


Registriert seit:
18.08.2002

Hessen
4389513
Re: Tipps und Hinweise für sauberen CodeNach oben.
Den Grundsatz breche ich
Ich kann bestens Programmieren und Planen während ich Death Metal höre.

-Markus-
04.08.2003, 13:49:03 Uhr
LunaticSystems
GepinntSeite: 1 2 3 4 >


ZFX Community Software, Version 0.9.1
Copyright 2002-2003 by Steffen Engel