Ist es eine schlechte Praxis, alle Aufzählungen in eine Datei aufzunehmen und in mehreren Klassen zu verwenden?

12

Ich bin ein aufstrebender Spieleentwickler, arbeite an gelegentlichen Indie-Spielen und mache seit einiger Zeit etwas, das anfangs wie eine schlechte Praxis schien, aber ich möchte wirklich eine Antwort von einigen erfahrenen Programmierern hier bekommen.

enumList.hAngenommen , ich habe eine Datei mit dem Namen, in der ich alle Aufzählungen deklariere, die ich in meinem Spiel verwenden möchte:

// enumList.h

enum materials_t { WOOD, STONE, ETC };
enum entity_t { PLAYER, MONSTER };
enum map_t { 2D, 3D };
// and so on.

// Tile.h
#include "enumList.h"
#include <vector>

class tile
{
    // stuff
};

Die Hauptidee ist, dass ich alle Aufzählungen im Spiel in einer Datei deklariere und diese Datei dann importiere, wenn ich eine bestimmte Aufzählung daraus verwenden muss, anstatt sie in der Datei zu deklarieren, in der ich sie verwenden muss. Ich mache das, weil es die Dinge sauber macht. Ich kann auf jede Aufzählung an einem Ort zugreifen, anstatt Seiten nur für den Zugriff auf eine Aufzählung öffnen zu lassen.

Ist dies eine schlechte Praxis und kann sie die Leistung in irgendeiner Weise beeinträchtigen?

Bugster
quelle
1
Die Quellstruktur kann die Leistung nicht beeinträchtigen - sie wird immer noch auf dieselbe kompiliert, unabhängig davon, wo sich die Aufzählungen befinden. Das ist also wirklich eine Frage, wo es am besten ist, sie zu platzieren, und für kleine Aufzählungen klingt eine Datei nicht zu tabu.
Steffan Donal
Ich habe in extremeren Fällen gefragt, ein Spiel kann viele Aufzählungen haben und sie können ziemlich groß sein, aber danke für Ihren Kommentar
Bugster
4
Dies hat keine Auswirkungen auf die Anwendungsleistung, wirkt sich jedoch negativ auf die Kompilierungszeit aus. Wenn Sie beispielsweise materials_tDateien, die sich nicht mit Materialien befassen , ein Material hinzufügen, müssen Sie es neu erstellen.
Gort the Robot
14
Es ist, als würden Sie alle Stühle in Ihrem Haus in den Stuhlraum stellen. Wenn Sie sich also hinsetzen möchten, wissen Sie, wohin Sie gehen müssen.
Kevin Cline
Abgesehen davon können Sie problemlos beides tun , indem Sie jede Aufzählung in eine eigene Datei einfügen und enumList.hals Sammlung von #includes für diese Dateien dienen. Dies ermöglicht Dateien, die nur eine Aufzählung benötigen, um sie direkt abzurufen, und bietet gleichzeitig ein einzigartiges Paket für alles, was wirklich alle möchte.
Justin Time - Stellen Sie Monica

Antworten:

35

Ich denke wirklich, dass es eine schlechte Praxis ist. Wenn wir die Codequalität messen, nennen wir das "Granularität". Ihre Granularität leidet stark, wenn Sie alle diese Aufzählungen in einer einzigen Datei zusammenfassen, und daher leidet auch die Wartbarkeit.

Ich hätte eine einzelne Datei pro Aufzählung, um sie schnell zu finden und sie mit dem Verhaltenscode der spezifischen Funktionalität zu gruppieren (z. B. Materialaufzählung in Ordnern, in denen sich das Materialverhalten befindet usw.).

Die Hauptidee ist, dass ich alle Aufzählungen im Spiel in einer Datei deklariere und diese Datei dann importiere, wenn ich eine bestimmte Aufzählung daraus verwenden muss, anstatt sie in der Datei zu deklarieren, in der ich sie verwenden muss. Ich mache das, weil es die Dinge sauber macht. Ich kann auf jede Aufzählung an einem Ort zugreifen, anstatt Seiten nur für den Zugriff auf eine Aufzählung öffnen zu lassen.

Sie könnten denken, es ist sauber, aber in der Tat ist es nicht. Es koppelt Dinge, die funktional und modulmäßig nicht zusammengehören, und verringert die Modularität Ihrer Anwendung. Abhängig von der Größe Ihrer Codebasis und der Modularität Ihres Codes kann dies zu einem größeren Problem und zu unreinen Codes / Abhängigkeiten in anderen Teilen Ihres Systems führen. Wenn Sie jedoch nur ein kleines monolithisches System schreiben, gilt dies nicht unbedingt. Aber ich würde es selbst für ein kleines monolithisches System nicht so machen.

Falke
quelle
2
+1 für die Erwähnung der Granularität habe ich die anderen Antworten berücksichtigt, aber Sie machen einen guten Punkt.
Bugster
+1 für eine einzelne Datei pro Aufzählung. Zum einen ist es leichter zu finden.
Mauris
11
Ganz zu schweigen davon, dass das Hinzufügen eines einzelnen Enum-Werts an einer Stelle dazu führt, dass jede Datei in Ihrem gesamten Projekt neu erstellt werden muss.
Gort the Robot
guter Rat. Sie haben es sich trotzdem anders überlegt. Ich habe es immer gemocht, CompanyNamespace.Enums ... zu besuchen und eine einfache Liste zu erhalten, aber wenn die
Matt Evans
21

Ja, es ist eine schlechte Praxis, nicht wegen der Leistung, sondern wegen der Wartbarkeit.

Es macht Dinge "sauber" nur in der OCD "ähnliche Dinge zusammen sammeln". Aber das ist eigentlich nicht die nützliche und gute Art von "Sauberkeit".

Code-Entitäten sollten gruppiert werden, um den Zusammenhalt zu maximieren und die Kopplung zu minimieren. Dies wird am besten erreicht, indem sie in weitgehend unabhängige Funktionsmodule gruppiert werden. Wenn Sie sie nach technischen Kriterien gruppieren (z. B. alle Aufzählungen zusammenfügen), wird das Gegenteil erreicht: Sie koppelt Code, der absolut keine funktionale Beziehung hat, und fügt Aufzählungen, die möglicherweise nur an einer Stelle verwendet werden, in eine andere Datei ein.

Michael Borgwardt
quelle
3
Wahr; "Nur in der OCD" sammle ähnliche Dinge zusammen "Weg". 100 positive Stimmen, wenn ich könnte.
Dogweather
Ich würde helfen, die Rechtschreibung zu bearbeiten, wenn ich könnte, aber Sie haben nicht genug Fehler gemacht, um den SE-Schwellenwert für "Bearbeiten" zu überschreiten. :-P
Dogweather
Interessant ist, dass bei fast allen Webframeworks Dateien nach Typ und nicht nach Funktion erfasst werden müssen.
Kevin Cline
@kevincline: Ich würde nicht "fast alle" sagen - nur diejenigen, die auf Konvention über Konfiguration basieren, und normalerweise haben diese auch ein Modulkonzept, das es ermöglicht, Code funktional zu gruppieren.
Michael Borgwardt
8

Nun, das sind nur Daten (kein Verhalten). Das Schlimmste, was möglicherweise / theoretisch passieren könnte, ist, dass derselbe Code mehr als einmal enthalten und kompiliert wird, wodurch ein relativ größeres Programm erstellt wird.

Es ist einfach unmöglich, dass solche Includes Ihren Vorgängen mehr Zyklen hinzufügen können, da sie keinen Verhaltens- / Verfahrenscode enthalten (keine Schleifen, keine Wenns usw.).

Ein (relativ) größeres Programm kann die Leistung (Ausführungsgeschwindigkeit) kaum beeinträchtigen und ist auf jeden Fall nur ein entferntes theoretisches Problem. Die meisten (möglicherweise alle) Compiler verwalten Includes so, dass solche Probleme vermieden werden.

IMHO, die Vorteile, die Sie mit einer einzelnen Datei (besser lesbarer und besser verwaltbarer Code) erhalten, übertreffen jeden möglichen Nachteil bei weitem.

AlexBottoni
quelle
5
Sie machen einen sehr guten Punkt, den ich denke, die populäreren Antworten ignorieren - unveränderliche Daten haben überhaupt keine Kopplung.
Michael Shaw
3
Ich denke, Sie mussten noch nie an Projekten arbeiten, deren Kompilierung eine halbe Stunde oder länger dauern würde. Wenn Sie alle Ihre Aufzählungen in einer Datei haben und eine einzelne Aufzählung ändern, ist es Zeit für eine lange Pause. Selbst wenn es nur eine Minute gedauert hat, ist das immer noch zu lang.
Dunk
2
Jeder, der unter Versionskontrolle in Modul X arbeitet, muss jedes Mal, wenn er arbeiten möchte, Modul X und die Aufzählung erhalten. Außerdem erscheint es mir als eine Form der Kopplung, wenn sich Ihre Änderung jedes Mal, wenn Sie die Enum-Datei ändern, möglicherweise auf jedes einzelne Modul in Ihrem Projekt auswirkt. Wenn Ihr Projekt groß genug ist, dass nicht alle oder die meisten Teammitglieder nicht jeden Teil des Projekts verstehen, ist eine globale Aufzählung ziemlich gefährlich. Eine globale unveränderliche Variable ist nicht annähernd so schlecht wie eine globale veränderbare Variable, aber immer noch nicht ideal. Allerdings ist eine globale Aufzählung in einem Team mit <10 Mitgliedern wahrscheinlich meistens in Ordnung.
Brian
3

Für mich hängt alles vom Umfang Ihres Projekts ab. Wenn es eine Datei mit beispielsweise 10 Strukturen gibt und dies die einzigen sind, die jemals verwendet wurden, wäre es mir sehr angenehm, eine .h-Datei dafür zu haben. Wenn Sie verschiedene Arten von Funktionen haben, z. B. Einheiten, Wirtschaft, Gebäude usw., würde ich die Dinge definitiv aufteilen. Erstellen Sie eine unit.h, in der sich alle Strukturen befinden, die sich mit Einheiten befassen. Wenn Sie irgendwo etwas mit Einheiten machen möchten, müssen Sie Einheiten einschließen.h sicher, aber es ist auch eine nette "Kennung", dass etwas mit Einheiten wahrscheinlich in dieser Datei gemacht wird.

Betrachten Sie es so, Sie kaufen keinen Supermarkt, weil Sie eine Dose Cola brauchen;)

hoppa
quelle
3

Ich bin kein C ++ - Entwickler, daher wird diese Antwort für OOA & D allgemeiner sein:

In der Regel sollten Codeobjekte nach funktionaler Relevanz gruppiert werden, nicht unbedingt nach sprachspezifischen Konstrukten. Die wichtigste Ja-Nein-Frage, die immer gestellt werden sollte, lautet: "Wird vom Endcodierer erwartet, dass er die meisten oder alle Objekte verwendet, die er beim Konsumieren einer Bibliothek erhält?" Wenn ja, gruppieren Sie sich. Wenn nicht, sollten Sie die Codeobjekte aufteilen und näher an anderen Objekten platzieren, die sie benötigen (wodurch sich die Wahrscheinlichkeit erhöht, dass der Verbraucher alles benötigt, auf das er tatsächlich zugreift).

Das Grundkonzept ist "hoher Zusammenhalt"; Codemitglieder (von Methoden einer Klasse bis hin zu Klassen in einem Namespace oder einer DLL und DLLs selbst) sollten so organisiert sein, dass ein Codierer alles enthalten kann, was er benötigt, und nichts, was er nicht benötigt. Dies macht das Gesamtdesign toleranter gegenüber Änderungen. Dinge, die sich ändern müssen, können sein, ohne andere Codeobjekte zu beeinflussen, die sich nicht ändern mussten. In vielen Fällen wird die Anwendung dadurch auch speichereffizienter. Eine DLL wird vollständig in den Speicher geladen, unabhängig davon, wie viele dieser Anweisungen jemals vom Prozess ausgeführt werden. Das Entwerfen von "schlanken" Anwendungen erfordert daher, darauf zu achten, wie viel Code in den Speicher gezogen wird.

Dieses Konzept gilt für praktisch alle Ebenen der Code-Organisation mit unterschiedlichen Auswirkungen auf Wartbarkeit, Speichereffizienz, Leistung, Erstellungsgeschwindigkeit usw. Wenn ein Codierer auf einen Header / eine DLL von eher monolithischer Größe verweisen muss, um auf ein Objekt zuzugreifen, Wenn dies nicht von einem anderen Objekt in dieser DLL abhängt, sollte die Aufnahme dieses Objekts in die DLL wahrscheinlich überdacht werden. Es ist jedoch möglich, auch in die andere Richtung zu weit zu gehen. Eine DLL für jede Klasse ist eine schlechte Idee, da sie die Erstellungsgeschwindigkeit verlangsamt (mehr DLLs müssen mit dem damit verbundenen Overhead neu erstellt werden) und die Versionierung zu einem Albtraum macht.

Ein typisches Beispiel: Wenn bei einer realen Verwendung Ihrer Codebibliothek die meisten oder alle Aufzählungen verwendet werden müssen, die Sie in diese einzelne Datei "enumerations.h" einfügen, gruppieren Sie sie auf jeden Fall. Sie werden wissen, wo Sie suchen müssen, um sie zu finden. Wenn Ihr konsumierender Codierer jedoch möglicherweise nur ein oder zwei der Dutzend Aufzählungen benötigt, die Sie in der Kopfzeile angeben, würde ich Sie ermutigen, diese in eine separate Bibliothek zu stellen und sie von den größeren Aufzählungen abhängig zu machen . Auf diese Weise können Ihre Codierer nur das eine oder andere erhalten, das sie möchten, ohne eine Verknüpfung mit der monolithischeren DLL herzustellen.

KeithS
quelle
2

Diese Art von Dingen wird zu einem Problem, wenn mehrere Entwickler an derselben Codebasis arbeiten.

Ich habe sicherlich gesehen, dass ähnliche globale Dateien zu einem Knotenpunkt für Zusammenführungskollisionen und allerlei Trauer bei (größeren) Projekten wurden.

Wenn Sie jedoch der einzige sind, der an dem Projekt arbeitet, sollten Sie alles tun, was Ihnen am besten gefällt, und nur dann "Best Practices" anwenden, wenn Sie die Motivation dahinter verstehen (und damit einverstanden sind).

Es ist besser, einige Fehler zu machen und daraus zu lernen, als zu riskieren, für den Rest Ihres Lebens an den Programmierpraktiken des Frachtkultes festzuhalten.

William Payne
quelle
1

Ja, es ist eine schlechte Praxis, dies bei einem großen Projekt zu tun. KUSS.

Der junge Mitarbeiter benannte eine einfache Variable in eine .h-Kerndatei um und 100 Ingenieure warteten 45 Minuten, bis alle ihre Dateien neu erstellt wurden, was sich auf die Leistung aller auswirkte. ;)

Alle Projekte beginnen im Laufe der Jahre klein und wir verfluchen diejenigen, die frühzeitig Abkürzungen für die Entstehung technischer Schulden genommen haben. Best Practices: Halten Sie globale .h-Inhalte auf das beschränkt, was unbedingt global ist.

AmyInNH
quelle
Sie verwenden kein Überprüfungssystem, wenn jemand (jung oder alt, dasselbe) nur (aus Spaß) alle dazu bringen kann, ein Projekt neu aufzubauen, nicht wahr?
Sanctus
1

In diesem Fall würde ich sagen, dass die ideale Antwort darin besteht, dass es davon abhängt, wie die Aufzählungen verbraucht werden. In den meisten Fällen ist es jedoch wahrscheinlich am besten, alle Aufzählungen separat zu definieren. Wenn jedoch eine der Aufzählungen bereits vom Design her gekoppelt ist, sollten Sie eine angeben Mittel zum gemeinsamen Einführen der gekoppelten Aufzählungen. Tatsächlich haben Sie eine Kopplungstoleranz bis zu dem Ausmaß der bereits vorhandenen absichtlichen Kopplung, jedoch nicht mehr.

In Anbetracht dessen ist es wahrscheinlich, dass die flexibelste Lösung wahrscheinlich jede Aufzählung in einer separaten Datei definiert, aber gekoppelte Pakete bereitstellt, wenn dies angemessen ist (wie durch die beabsichtigte Verwendung der beteiligten Aufzählungen bestimmt).


Alle Ihre Aufzählungen in der gleichen Datei Paare definieren sie zusammen, und durch die Erweiterung verursacht jeder Code, unabhängig davon , ob der Code tatsächlich auf allen Aufzählungen abhängig von einem oder mehreren Aufzählungen abhängig nutzt alle anderen Aufzählungen.

#include "enumList.h"

// Draw map texture.  Requires map_t.
// Not responsible for rendering entities, so doesn't require other enums.
// Introduces two unnecessary couplings.
void renderMap(map_t, mapIndex);

renderMap()Ich würde viel lieber nur darüber Bescheid wissen map_t, da sich sonst Änderungen an den anderen darauf auswirken, obwohl sie nicht wirklich mit den anderen interagieren.

#include "mapEnum.h" // Theoretical file defining map_t.

void renderMap(map_t, mapIndex);

In dem Fall, in dem Komponenten bereits miteinander gekoppelt sind, kann die Bereitstellung mehrerer Aufzählungen in einem einzigen Paket leicht zusätzliche Klarheit und Einfachheit bieten, vorausgesetzt, es gibt einen klaren logischen Grund für die Kopplung der Aufzählungen, und die Verwendung dieser Aufzählungen ist ebenfalls gekoppelt. und dass das Bereitstellen von ihnen auch keine zusätzlichen Kupplungen einführt.

#include "entityEnum.h"    // Theoretical file defining entity_t.
#include "materialsEnum.h" // Theoretical file defining materials_t.

// Can entity break the specified material?
bool canBreakMaterial(entity_t, materials_t);

In diesem Fall besteht keine direkte logische Verbindung zwischen Entitätstyp und Materialtyp (vorausgesetzt, die Entitäten bestehen nicht aus einem der definierten Materialien). Wenn wir jedoch einen Fall hatten, in dem beispielsweise eine Aufzählung explizit von der anderen abhängig ist, ist es sinnvoll, ein einzelnes Paket bereitzustellen, das alle gekoppelten Aufzählungen (sowie alle anderen gekoppelten Komponenten) enthält, damit die Kopplung erfolgen kann zu diesem Paket so weit wie möglich isoliert.

// File: "actionEnums.h"

enum action_t { ATTACK, DEFEND, SKILL, ITEM };               // Action type.
enum skill_t  { DAMAGE, HEAL, BUFF, DEBUFF, INFLICT, NONE }; // Skill subtype.

// -----

#include "actionTypes.h" // Provides action_t & skill_t from "actionEnums.h", and class Action (which couples them).
#include "entityEnum.h"  // Theoretical file defining entity_t.

// Assume ActFlags is or acts as a table of flags indicating what is and isn't allowable, based on entity_t and Action.
ImplementationDetail ActFlags;

// Indicate whether a given type of entity can perform the specified action type.
// Assume class Action provides members type() and subtype(), corresponding to action_t and skill_t respectively.
// Is only slightly aware of the coupling; knows type() and subtype() are coupled, but not how or why they're coupled.
bool canAct(entity_t e, const Action& act) {
    return ActFlags[e][act.type()][act.subtype()];
}

Aber leider ... selbst wenn zwei Aufzählungen eng miteinander verbunden sind, selbst wenn es so stark ist wie "zweite Aufzählung liefert Unterkategorien für erste Aufzählung", kann es immer noch zu einer Zeit kommen, in der nur eine der Aufzählungen erforderlich ist.

#include "actionEnums.h"

// Indicates whether a skill can be used from the menu screen, based on the skill's type.
// Isn't concerned with other action types, thus doesn't need to be coupled to them.
bool skillUsableOnMenu(skill_t);

// -----
// Or...
// -----

#include "actionEnums.h"
#include "gameModeEnum.h" // Defines enum gameMode_t, which includes MENU, CUTSCENE, FIELD, and BATTLE.

// Used to grey out blocked actions types, and render them unselectable.
// All actions are blocked in cutscene, or allowed in battle/on field.
// Skill and item usage is allowed in menu.  Individual skills will be checked on attempted use.
// Isn't concerned with specific types of skills, only with broad categories.
bool actionBlockedByGameMode(gameMode_t mode, action_t act) {
    if (mode == CUTSCENE) { return true; }
    if (mode == MENU) { return (act == SKILL || act == ITEM); }

    //assert(mode == BATTLE || mode == FIELD);
    return false;
}

Da wir beide wissen, dass es immer Situationen geben kann, in denen das Definieren mehrerer Aufzählungen in einer einzelnen Datei zu unnötiger Kopplung führen kann und das Bereitstellen gekoppelter Aufzählungen in einem einzigen Paket die beabsichtigte Verwendung verdeutlichen und es uns ermöglichen kann, den tatsächlichen Kopplungscode selbst als zu isolieren Die ideale Lösung besteht darin, jede Aufzählung separat zu definieren und gemeinsame Pakete für alle Aufzählungen bereitzustellen, die häufig zusammen verwendet werden sollen. Die einzigen Aufzählungen, die in derselben Datei definiert sind, sind solche, die eng miteinander verbunden sind, sodass die Verwendung der einen auch die Verwendung der anderen erfordert.

// File: "materialsEnum.h"
enum materials_t { WOOD, STONE, ETC };

// -----

// File: "entityEnum.h"
enum entity_t { PLAYER, MONSTER };

// -----

// File: "mapEnum.h"
enum map_t { 2D, 3D };

// -----

// File: "actionTypesEnum.h"
enum action_t { ATTACK, DEFEND, SKILL, ITEM };

// -----

// File: "skillTypesEnum.h"
enum skill_t  { DAMAGE, HEAL, BUFF, DEBUFF, INFLICT, NONE };

// -----

// File: "actionEnums.h"
#include "actionTypesEnum.h"
#include "skillTypesEnum.h"
Justin Time - Monica wieder einsetzen
quelle