Designfrage: Anpassbare Codestellen zusammenfassen oder dokumentieren?
-
Ich schreibe derzeit ein etwas größeres Projekt von dem ich erwarte, dass andere Leute später eigene Anpassungen vornehmen werden. Aufgrund der Art des Projekts, kann ich aber schon ziemlich sicher abschätzen, welcher Art diese Anpassungen sein werden. Nun könnte ich alle relevanten Codestellen zentral zusammenfassen, z.B. in einer Datei "Hier_kann_man_sinnvolle_Anpassungen_vornehmen.h", die dann ausreichend kommentiert ist. Oder ich könnte eine ausführliche Doku schreiben, in der dann solche Sachen auftauchen: "Sie wollen A ändern? Dann müssen sie in Datei B dies und das machen und in Datei C dieses und jenes. Aber vergessen Sie dann bloß nicht die Änderung in Datei D einzutragen."
Ich sehe für die erste Variante den Vorteil, dass selbst unerfahrene Programmierer leicht eigene Änderungen vornehmen können. Andererseits wird dadurch jedoch die Struktur des Projektes gestört, wodurch von mir unvorhergesehene Änderungen schwierig werden, da nicht mehr klar ist, wo welcher Code steht.
Bei Variante 2 hat ein erfahrener Programmierer sicherlich keine Probleme die vorgesehenen Änderungen durchzuführen, außerdem kann er leichter an anderen Stellen eigene Änderungen durchführen, weil der Code klarer modularisiert ist. Ein unerfahrener Programmierer wird aber sicherlich einiges falsch machen und weiß dann gar nicht mehr, wie er etwas ändern soll.
Wie haltet ihr es mit solchen Dingen? Was würdet ihr empfehlen? Ich bin ja zur Variante 2 geneigt, obwohl ich teilweise mit recht unerfahrenen Benutzern rechne.
Grüße,
SeppJ
-
Sofern es nicht möglich ist, das ganze so zu designen, dass du ein Interface hast über das du das Verhalten entsprechend verändern kannst, ohne an dem bestehendem Code rumzufummeln halte ich es für sinnvoll diese Codestellen zusammenzufassen und ggf. noch aus der bestehenden Struktur richtig zu lösen. Aber dokumentieren würd ich sowieso..
-
Also ich würde auch zu Schnittstellen tendieren. Rumfummeln von zu vielen Leuten im gleichen Code endet meiner Meinung nach eher katastrophal. Mir wäre auch nicht bekannt, dass man etwas nicht ohne Schnittstellen lösen könnte. Für Typ Anpassungen gibt es Templateparameter, für andere Werte gibt es Setter und Getter und für andere Algos virtuelle Methoden, usw. usf.
Kannst du vielleicht das Problem etwas näher beleuchten, also worum es hier genauer geht?
Grüssli
-
Sie werden eh überall ändern. Und der Code wird teilweise zum Heulen sein. Wie man neue Klassen zufügt, hatte ich auf AFAIR 7 Seiten externer Doku geschrieben.
Ein Jahr später sah ich, wie sie mir die Kernklassen getunt hatten, mit Einganswarteschlangen per#define MAKE_QUEUE(TYPE)\ ...Ich würde immer wieder die Doku wählen, als ausführliches und möglichst narrensicheres Tutorial.
-
Dravere schrieb:
Kannst du vielleicht das Problem etwas näher beleuchten, also worum es hier genauer geht?
Konkret geht es um eine Simulation verschiedener Objekte, die miteinander interagieren. Ich könnte mir vorstellen, dass andere Benutzer später neue Objekte hinzufügen werden, die dann wiederum auf eine gewisse Art miteinander interagieren. Ich erwarte aber nicht, dass das grundlegende Framework für die Simulation sich ändern wird.
Ich habe das Interface bisher nach meinem besten Wissen abstrakt gehalten. Da es bei der Sache aber schon ziemlich auf Performance ankommt, wollte ich rechenintensive Techniken, wie eine Virtualisierungsschicht zur Laufzeit, vermeiden. Irgendwo muss früher oder später in den Code eingegriffen werden, um neue Klassen und deren Verhalten bekannt zu machen.
Konkret müsste man folgende Komponenten ändern, um etwas hinzuzufügen:
- Das Interface für die Objektklasse implementieren
- Die neue Klasse in die Liste der zu verwendenden Objekttypen eintragen
- Der Interaktionsmatrix das neue Objekt und die Interaktionen zugehörigen hinzufügen
- Die neuen Interaktionen implementierenDas sind vom Aufbau des Simulators her eigentlich getrennt Bereiche, aber ich könnte natürlich diese Teile auch in eine einzelne Datei schreiben.
Und Danke auch an die anderen Meinungen im Thread, auch wenn sie sich widersprechen, vielleicht wird mit diesen Zusatzinfos das Bild ja eindeutiger.
-
SeppJ schrieb:
Dravere schrieb:
Kannst du vielleicht das Problem etwas näher beleuchten, also worum es hier genauer geht?
Konkret geht es um eine Simulation verschiedener Objekte, die miteinander interagieren. Ich könnte mir vorstellen, dass andere Benutzer später neue Objekte hinzufügen werden, die dann wiederum auf eine gewisse Art miteinander interagieren. Ich erwarte aber nicht, dass das grundlegende Framework für die Simulation sich ändern wird.
Ich habe das Interface bisher nach meinem besten Wissen abstrakt gehalten. Da es bei der Sache aber schon ziemlich auf Performance ankommt, wollte ich rechenintensive Techniken, wie eine Virtualisierungsschicht zur Laufzeit, vermeiden. Irgendwo muss früher oder später in den Code eingegriffen werden, um neue Klassen und deren Verhalten bekannt zu machen.
Konkret müsste man folgende Komponenten ändern, um etwas hinzuzufügen:
- Das Interface für die Objektklasse implementieren
- Die neue Klasse in die Liste der zu verwendenden Objekttypen eintragen
- Der Interaktionsmatrix das neue Objekt und die Interaktionen zugehörigen hinzufügen
- Die neuen Interaktionen implementierenDas sind vom Aufbau des Simulators her eigentlich getrennt Bereiche, aber ich könnte natürlich diese Teile auch in eine einzelne Datei schreiben.
Und Danke auch an die anderen Meinungen im Thread, auch wenn sie sich widersprechen, vielleicht wird mit diesen Zusatzinfos das Bild ja eindeutiger.
Wenn es dir so auf Performance ankommt, kannst du nicht einfach eine Scriptsprache verwenden, die am Anfang alles initialisiert und dann damit arbeitet?
-
CodeDown schrieb:
Wenn es dir so auf Performance ankommt, kannst du nicht einfach eine Scriptsprache verwenden, die am Anfang alles initialisiert und dann damit arbeitet?
Ich verstehe nicht, was du damit meinst, kannst du das erläutern?
-
Ich erkläre mal an einem einfachen Beispiel, wie es für mich anhört, was du machen willst und wie ich es lösen würde:
Du hast Einheiten (class Unit), wie zum Beispiel einen Schwertkämpfer (class Swordsman : public Unit) oder Bogenschützen (class Bowman : public Unit). Wenn du es so aufbaust, wie ich es in den Klammern hingeschrieben habe, dann musst du tatsächlich immer eine eigene Klasse für jede neue Einheit implementieren. Viel einfacher wäre es doch, wenn du nur die KlasseUnithättest, welche alle benötigten Eigenschaften hat. Über zum Beispiel ein XML File könnte man nun seine Einheiten definieren. Diese werden alsUnitObjekte angelegt und registriert. So kann man einfach über XML die Objekte verändern. Geladen und initialisiert wird zu Begin, wodurch kein Performanceverlust zur Laufzeit passiert.Für unterschiedliche Algorithmen wären da durchaus Skriptsprachen von Vorteil. Du kompilierst oder übersetzt die Skriptsprache einfach zu Begin und hälst sie dann in einer effektiven Form. Ähnlich wie es auch gewisse JITs machen, zum Beispiel bei C#. Es kommt da halt ein wenig auf den Umfang an. Vielleicht hilft es auch nur, wenn man die Eigenschaften selber einstellen kann und keine eigenen Algorithmen definieren muss.
Du könntest dem Benutzer der Bibliothek natürlich auch die Möglichkeit geben, Algorithmen in C++ zu definieren und sie dann als Name dem Parser übergeben. Damit wäre es möglich aus dem XML File heraus einfach nur der Name des Algorithmus zu definieren, wodurch dieser über Funktionszeiger oder ähnliches aufgerufen wird.
Ich hoffe, dass ich ein paar Ansätze geben konnte, welche Richtungen es hier geben könnte.
Grüssli
-
Dravere schrieb:
Ich erkläre mal an einem einfachen Beispiel, wie es für mich anhört, was du machen willst und wie ich es lösen würde:
Du hast Einheiten (class Unit), wie zum Beispiel einen Schwertkämpfer (class Swordsman : public Unit) oder Bogenschützen (class Bowman : public Unit). Wenn du es so aufbaust, wie ich es in den Klammern hingeschrieben habe, dann musst du tatsächlich immer eine eigene Klasse für jede neue Einheit implementieren. Viel einfacher wäre es doch, wenn du nur die KlasseUnithättest, welche alle benötigten Eigenschaften hat. Über zum Beispiel ein XML File könnte man nun seine Einheiten definieren. Diese werden alsUnitObjekte angelegt und registriert. So kann man einfach über XML die Objekte verändern. Geladen und initialisiert wird zu Begin, wodurch kein Performanceverlust zur Laufzeit passiert.Das klingt nach einer guten Idee. Momentan mache ich es mit meinen eigenen Objekten schon ähnlich (aber umständlicher), da muss ich nur wenig ändern um das zu verbessern.
Für unterschiedliche Algorithmen wären da durchaus Skriptsprachen von Vorteil. Du kompilierst oder übersetzt die Skriptsprache einfach zu Begin und hälst sie dann in einer effektiven Form. Ähnlich wie es auch gewisse JITs machen, zum Beispiel bei C#. Es kommt da halt ein wenig auf den Umfang an. Vielleicht hilft es auch nur, wenn man die Eigenschaften selber einstellen kann und keine eigenen Algorithmen definieren muss.
Ich glaube das Erzeugen von gutem Maschinencode geht weit über meine Kenntnisse hinaus, vor allem da das ganze plattformunabhängig sein muss. Wenn ich bedenke, viele Entwicklerjahrhunderte beispielsweise Java gebraucht hat bis der JIT-Compiler einigermaßen flott war, kann ich da als Einzelner wohl nicht mithalten. Oder habe ich dich hier falsch verstanden?
Du könntest dem Benutzer der Bibliothek natürlich auch die Möglichkeit geben, Algorithmen in C++ zu definieren und sie dann als Name dem Parser übergeben. Damit wäre es möglich aus dem XML File heraus einfach nur der Name des Algorithmus zu definieren, wodurch dieser über Funktionszeiger oder ähnliches aufgerufen wird.
Das klingt schon besser umsetzbar, ich werde mal prüfen, wieviel Aufwand das wäre.
-
SeppJ schrieb:
Ich glaube das Erzeugen von gutem Maschinencode geht weit über meine Kenntnisse hinaus, vor allem da das ganze plattformunabhängig sein muss. Wenn ich bedenke, viele Entwicklerjahrhunderte beispielsweise Java gebraucht hat bis der JIT-Compiler einigermaßen flott war, kann ich da als Einzelner wohl nicht mithalten. Oder habe ich dich hier falsch verstanden?
Du musst ja nicht gleich Maschinencode erzeugen

Aber du kannst die Skriptsprache parsen und daraus einen Ablaufplan erstellen und diesen etwas optimieren. Da ist schon vieles mit relativ wenig Mitteln möglich. Vor allem wenn du die Skriptsprache nicht zu komplex machst und vielleicht gibt es dafür bereits schon fertig Bibliotheken.Es geht halt vor allem darum, dass du nicht jedes Mal das Skript neu parst, wieder interpretierst und ausführst. Am besten das Result von parsen und interpretieren, gegebenenfalls leicht optimieren, abspeichern und dann jeweils nur noch ausführen.
Grüssli
-
Ahh, verstehe. Ja, so eine Skriptsprache wäre tatsächlich innerhalb meiner Möglichkeiten, ich schätze ich würde mich dadurch aber zu sehr einschränken. Um nochmal das Spielbeispiel zu verwenden: Derzeit mögen meine Einheiten zwar noch Schwertkämpfer und Bogenschütze sein und ich könnte mir denken eine Skriptsprache zu entwickeln die beide beschreibt aber auch Katapulte und Düsenjäger erlaubt. Es könnte bei mir aber auch eines Tages als Einheit Telefone, Philosophie oder die Farbe Blau hinzukommen. Ich habe jetzt schon so ein Objekt dabei, das in gar kein Schema passt (bis auf wenige Eigenschaften) und an (fast) jeder Stelle eine Sonderbehandlung benötigt. Ich fürchte ich brauche für neue Einheiten wirklich die Mächtigkeit einer ausgewachsenen Programmiersprache.
-
SeppJ schrieb:
Es könnte bei mir aber auch eines Tages als Einheit Telefone, Philosophie oder die Farbe Blau hinzukommen.

Ehm, also irgendwo muss es doch gemeinsame Nenner geben, sonst kann es deine Engine gar nicht behandeln

Naja, da kann ich grundsätzlich jetzt nur noch als zusätzlicher Tipp geben: Habe keine Angst vor virutellen Funktionen und Funktionszeiger wegen der Performance. Die kann man in dem Fall getrost vernachlässigen. Es gilt hier das 80-20 Prinzip. 80% der Zeit wird in 20% des Codes verbracht. Zudem optimiere erst richtig, wenn du mit einem Profiler drüber bist. Vorausüberlegungen schaden natürlich nicht, aber wenn man dann anfängt virtuelle Funktionen im Voraus auszuschliessen, dann geht man definitiv zu weit
Grüssli
-
Dravere schrieb:
SeppJ schrieb:
Es könnte bei mir aber auch eines Tages als Einheit Telefone, Philosophie oder die Farbe Blau hinzukommen.

Ehm, also irgendwo muss es doch gemeinsame Nenner geben, sonst kann es deine Engine gar nicht behandeln

Naja, da kann ich grundsätzlich jetzt nur noch als zusätzlicher Tipp geben: Habe keine Angst vor virutellen Funktionen und Funktionszeiger wegen der Performance. Die kann man in dem Fall getrost vernachlässigen. Es gilt hier das 80-20 Prinzip. 80% der Zeit wird in 20% des Codes verbracht. Zudem optimiere erst richtig, wenn du mit einem Profiler drüber bist. Vorausüberlegungen schaden natürlich nicht, aber wenn man dann anfängt virtuelle Funktionen im Voraus auszuschliessen, dann geht man definitiv zu weit
Grüssli
Warum muss ich nur an zwei Worte denken, die man hier nicht schreiben darf, weil sonst Volkard alles löscht.
-
Dravere schrieb:
SeppJ schrieb:
Es könnte bei mir aber auch eines Tages als Einheit Telefone, Philosophie oder die Farbe Blau hinzukommen.

Ehm, also irgendwo muss es doch gemeinsame Nenner geben, sonst kann es deine Engine gar nicht behandeln

Naja, da kann ich grundsätzlich jetzt nur noch als zusätzlicher Tipp geben: Habe keine Angst vor virutellen Funktionen und Funktionszeiger wegen der Performance. Die kann man in dem Fall getrost vernachlässigen. Es gilt hier das 80-20 Prinzip. 80% der Zeit wird in 20% des Codes verbracht. Zudem optimiere erst richtig, wenn du mit einem Profiler drüber bist. Vorausüberlegungen schaden natürlich nicht, aber wenn man dann anfängt virtuelle Funktionen im Voraus auszuschliessen, dann geht man definitiv zu weit
Grüssli
Ja, war vielleicht ein bisschen übertrieben :D, natürlich gibt es einen gemeinsamen Nenner, an manchen Stellen sind bei einigen Sachen aber tatsächlich Sonderbehandlungen angesagt. Insofern tendiere ich auch eher zu der zweiten Möglichkeit bei meiner Ursprungsfrage, weil manchmal dann doch an unvorhergesehenen Stellen eingegriffen werden muss.
Ich werde das mit der Performance der virtuellen Funktionen noch mal am halbfertigen Projekt testen, damit würde einiges einfacher sein. Ich hatte es im vorhinein schon mal probiert, aber meine Testfunktionen hatten deutlich weniger Anweisungen als die fertigen Funktionen, insofern war der Overhead relativ größer und die Performance entsprechend schwach. Ich hatte mich damals aus diesem Grund gegen virtuelle Funktionen entschieden.