Est-ce une mauvaise pratique d'inclure toutes les énumérations dans un fichier et de l'utiliser dans plusieurs classes?

12

Je suis un développeur de jeux en herbe, je travaille sur des jeux indépendants occasionnels, et depuis un moment, je fais quelque chose qui semblait être une mauvaise pratique au début, mais je veux vraiment obtenir une réponse de certains programmeurs expérimentés ici.

Disons que j'ai un fichier appelé enumList.hoù je déclare toutes les énumérations que je veux utiliser dans mon jeu:

// 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
};

L'idée principale est que je déclare toutes les énumérations du jeu dans un fichier , puis que j'importe ce fichier lorsque j'ai besoin d'en utiliser une certaine énumération, plutôt que de le déclarer dans le fichier où je dois l'utiliser. Je le fais parce que cela rend les choses propres, je peux accéder à chaque énumération en un seul endroit plutôt que d'avoir des pages ouvertes uniquement pour accéder à une énumération.

Est-ce une mauvaise pratique et peut-elle affecter les performances de quelque manière que ce soit?

Bugster
la source
1
La structure source ne peut pas affecter les performances - elle sera toujours compilée de la même manière, peu importe où se trouvent les énumérations. Il s'agit donc vraiment de savoir où il serait préférable de les mettre, et pour les petites énumérations, un fichier ne semble pas trop tabou.
Steffan Donal
Je demandais dans des cas plus extrêmes, un jeu peut avoir plusieurs énumérations et elles peuvent être assez grandes, mais merci pour votre commentaire
Bugster
4
Cela n'affectera pas les performances de l'application, mais cela aura un effet négatif sur le temps de compilation. Par exemple, si vous ajoutez un matériau à des materials_tfichiers qui n'en traitent pas, il devra être reconstruit.
Gort le robot
14
c'est comme mettre toutes les chaises de votre maison dans la salle du fauteuil, donc si vous voulez vous asseoir, vous savez où aller.
kevin Cline
En passant, vous pouvez facilement faire les deux , en mettant chaque énumération dans son propre fichier et en enumList.hservant de collection de #includes pour ces fichiers. Cela permet aux fichiers qui n'ont besoin que d'une seule énumération de l'obtenir directement, tout en fournissant un package singulier pour tout ce qui les désire vraiment tous.
Justin Time - Réintègre Monica

Réponses:

35

Je pense vraiment que c'est une mauvaise pratique. Lorsque nous mesurons la qualité du code, il y a ce que nous appelons la «granularité». Votre granularité souffre gravement en mettant toutes ces énumérations dans un seul fichier et donc la maintenabilité en souffre également.

J'aurais un seul fichier par énumération pour le trouver rapidement et le regrouper avec le code comportemental de la fonctionnalité spécifique (par exemple, énumération des matériaux dans le dossier où se trouve le comportement des matériaux, etc.);

L'idée principale est que je déclare toutes les énumérations du jeu dans un fichier, puis que j'importe ce fichier lorsque j'ai besoin d'en utiliser une certaine énumération, plutôt que de le déclarer dans le fichier où je dois l'utiliser. Je le fais parce que cela rend les choses propres, je peux accéder à chaque énumération en un seul endroit plutôt que d'avoir des pages ouvertes uniquement pour accéder à une énumération.

Vous pourriez penser que c'est propre, mais en fait, ce n'est pas le cas. C'est coupler des choses qui ne vont pas ensemble au niveau des fonctionnalités et des modules et diminue la modularité de votre application. En fonction de la taille de votre base de code et de la façon dont vous souhaitez que votre code soit structuré, cela peut évoluer vers un problème plus important et un code / des dépendances impurs dans d'autres parties de votre système. Cependant, si vous écrivez simplement un petit système monolithique, cela ne s'applique pas nécessairement. Pourtant, je ne le ferais pas comme ça même pour un petit système monolithique.

Faucon
la source
2
+1 pour mentionner la granularité, j'ai pris les autres réponses en considération mais vous faites un bon point.
Bugster
+1 pour un seul fichier par énumération. d'une part, il est plus facile à trouver.
mauris
11
Sans oublier que l'ajout d'une seule valeur d'énumération utilisée au même endroit entraînera la reconstruction de tous les fichiers de l'ensemble de votre projet.
Gort le robot
bon conseil. vous avez quand même changé d'avis .. J'ai toujours aimé aller sur CompanyNamespace.Enums .... et obtenir une liste facile, mais si la structure du code est disciplinée, alors votre approche est meilleure
Matt Evans
21

Oui, c'est une mauvaise pratique, non pas à cause des performances mais à cause de la maintenabilité.

Il rend les choses "propres" uniquement dans le TOC "rassemble des choses similaires". Mais ce n'est en fait pas le type de "propreté" utile et bon.

Les entités de code doivent être regroupées pour maximiser la cohésion et minimiser le couplage , ce qui est mieux réalisé en les regroupant en modules fonctionnels largement indépendants. Les regrouper selon des critères techniques (tels que le regroupement de toutes les énumérations) aboutit à l'inverse - il couple le code qui n'a absolument aucune relation fonctionnelle et place les énumérations qui ne peuvent être utilisées qu'à un seul endroit dans un fichier différent.

Michael Borgwardt
la source
3
Vrai; 'uniquement dans le TOC "collecter des choses similaires ensemble" ". 100 votes positifs si je le pouvais.
Dogweather
Je vous aiderais à modifier l'orthographe si je le pouvais, mais vous n'avez pas fait suffisamment d'erreurs pour dépasser le seuil de «modification» SE. :-P
Dogweather
Il est intéressant de noter que presque tous les frameworks Web nécessitent que les fichiers soient collectés par type plutôt que par fonctionnalité.
kevin cline
@kevincline: Je ne dirais pas "presque tous" - seulement ceux qui sont basés sur une convention sur configuration, et généralement, ceux-ci ont également un concept de module qui permet de regrouper le code de manière fonctionnelle.
Michael Borgwardt
8

Eh bien, ce ne sont que des données (pas un comportement). Le pire qui pourrait / pourrait théoriquement se produire est que le même code est inclus et compilé plus d'une fois produisant un programme relativement plus grand.

Il est tout simplement impossible que ces inclusions puissent ajouter plus de cycles à vos opérations, étant donné qu'il n'y a pas de code comportemental / procédural à l'intérieur (pas de boucles, pas de if, etc.).

Un programme (relativement) plus grand peut difficilement affecter les performances (vitesse d'exécution) et, en tout cas, n'est qu'une préoccupation théorique éloignée. La plupart des compilateurs (peut-être tous) gèrent les inclusions de manière à éviter de tels problèmes.

À mon humble avis, les avantages que vous obtenez avec un seul fichier (code plus lisible et plus gérable) dépassent largement tout inconvénient possible.

AlexBottoni
la source
5
Vous soulevez un très bon point que je pense que toutes les réponses les plus populaires ignorent - les données immuables n'ont aucun couplage du tout.
Michael Shaw
3
Je suppose que vous n'avez jamais eu à travailler sur des projets qui prendraient une demi-heure ou plus à compiler. Si vous avez toutes vos énumérations dans un seul fichier et modifiez une seule énumération, il est temps de faire une longue pause. Heck, même si cela n'a pris qu'une minute, c'est encore trop long.
Dunk
2
Toute personne travaillant dans le module X sous contrôle de version doit obtenir le module X et l'énumération à chaque fois qu'elle souhaite travailler. En outre, cela me semble être une forme de couplage si, chaque fois que vous modifiez le fichier enum, votre modification affecte potentiellement chaque module de votre projet. Si votre projet est suffisamment volumineux pour que tous ou la plupart des membres de l'équipe ne comprennent pas tous les morceaux du projet, une énumération globale est assez dangereuse. Une variable globale immuable n'est même pas aussi mauvaise qu'une variable globale mutable, mais elle n'est toujours pas idéale. Cela dit, une énumération mondiale est probablement la plupart du temps correcte dans une équipe de moins de 10 membres.
Brian
3

Pour moi, tout dépend de la portée de votre projet. S'il y a un fichier avec disons 10 structures et que ce sont les seuls jamais utilisés, je serais parfaitement à l'aise d'avoir un fichier .h pour cela. Si vous avez plusieurs types de fonctionnalités, par exemple des unités, une économie, des bâtiments, etc., je diviserais définitivement les choses. Créez un units.h où résident toutes les structures qui traitent des unités. Si vous voulez faire quelque chose avec des unités quelque part, vous devez inclure units.h bien sûr, mais c'est aussi un "identifiant" sympa que quelque chose avec des unités soit probablement fait dans ce fichier.

Regardez-le de cette façon, vous n'achetez pas de supermarché parce que vous avez besoin d'une canette de coca;)

hoppa
la source
3

Je ne suis pas un développeur C ++, donc cette réponse sera plus générique pour OOA & D:

En règle générale, les objets de code doivent être regroupés en termes de pertinence fonctionnelle, pas nécessairement en termes de constructions spécifiques au langage. La question clé oui-non qui devrait toujours être posée est la suivante: "le codeur final devra-t-il utiliser la plupart ou la totalité des objets qu'il obtient lors de la consommation d'une bibliothèque?" Si oui, groupez-vous. Sinon, envisagez de diviser les objets de code et de les placer plus près des autres objets qui en ont besoin (augmentant ainsi les chances que le consommateur ait besoin de tout ce à quoi il accède réellement).

Le concept de base est "haute cohésion"; les membres de code (des méthodes d'une classe jusqu'aux classes dans un espace de noms ou une DLL, et les DLL elles-mêmes) doivent être organisés de telle sorte qu'un codeur puisse inclure tout ce dont ils ont besoin, et rien qu'ils ne le font pas. Cela rend la conception globale plus tolérante au changement; les choses qui doivent changer peuvent être, sans affecter d'autres objets de code qui n'ont pas eu à changer. Dans de nombreuses circonstances, cela rend également l'application plus efficace en mémoire; une DLL est chargée dans son intégralité en mémoire, quelle que soit la quantité de ces instructions exécutées par le processus. La conception d'applications "lean" nécessite donc de prêter attention à la quantité de code qui est tirée en mémoire.

Ce concept s'applique à pratiquement tous les niveaux d'organisation du code, avec des degrés variables d'impact sur la maintenabilité, l'efficacité de la mémoire, les performances, la vitesse de génération, etc. qui ne dépend d'aucun autre objet dans cette DLL, alors l'inclusion de cet objet dans la DLL devrait probablement être repensée. Cependant, il est également possible d'aller trop loin dans l'autre sens; une DLL pour chaque classe est une mauvaise idée, car elle ralentit la vitesse de construction (plus de DLL à reconstruire avec la surcharge associée) et fait du versioning un cauchemar.

Exemple: si une utilisation réelle de votre bibliothèque de codes impliquait l'utilisation de la plupart ou de la totalité des énumérations que vous placez dans ce fichier "enumerations.h" unique, alors par tous les moyens, regroupez-les; vous saurez où chercher pour les trouver. Mais si votre codeur consommateur pouvait théoriquement avoir besoin d'une ou deux des douzaines d'énumérations que vous fournissez dans l'en-tête, alors je vous encouragerais à les mettre dans une bibliothèque distincte et à en faire une dépendance de la plus grande avec le reste des énumérations. . Cela permet à vos codeurs d'obtenir uniquement celui ou les deux qu'ils souhaitent sans se lier à la DLL plus monolithique.

KeithS
la source
2

Ce genre de chose devient un problème lorsque plusieurs développeurs travaillent sur la même base de code.

J'ai certainement vu des fichiers mondiaux similaires devenir un lien pour les collisions de fusion et toutes sortes de problèmes sur des projets (plus importants).

Cependant, si vous êtes le seul à travailler sur le projet, vous devez faire ce qui vous convient le mieux et n'adopter des "meilleures pratiques" que si vous comprenez (et êtes d'accord avec) la motivation qui les sous-tend.

Il vaut mieux faire des erreurs et en tirer des leçons que de risquer de rester coincé avec des pratiques de programmation culte du fret pour le reste de votre vie.

William Payne
la source
1

Oui, c'est une mauvaise pratique de le faire sur un grand projet. BAISER.

Un jeune collègue a renommé une simple variable dans un fichier .h de base et 100 ingénieurs ont attendu 45 minutes pour que tous leurs fichiers soient reconstruits, ce qui a affecté les performances de chacun. ;)

Tous les projets commencent petit, ballon au fil des ans, et nous maudissons ceux qui ont pris des raccourcis précoces pour créer de la dette technique. Meilleures pratiques, limitez le contenu .h mondial à ce qui est nécessairement mondial.

AmyInNH
la source
Vous n'utilisez pas le système de révision si quelqu'un (jeune ou vieux, le même) peut simplement (par plaisir) faire reconstruire le projet par tout le monde, n'est-ce pas?
Sanctus
1

Dans ce cas, je dirais que la réponse idéale est que cela dépend de la façon dont les énumérations sont consommées, mais que dans la plupart des cas, il est probablement préférable de définir toutes les énumérations séparément, mais si l'une d'entre elles est déjà couplée par conception, vous devez fournir un des moyens pour introduire collectivement lesdits énumérations couplées. En effet, vous avez une tolérance de couplage jusqu'à la quantité de couplage intentionnel déjà présente, mais pas plus.

Compte tenu de cela, la solution la plus flexible est susceptible de définir chaque énumération dans un fichier distinct, mais de fournir des packages couplés lorsque cela est raisonnable (tel que déterminé par l'utilisation prévue des énumérations impliquées).


La définition de toutes vos énumérations dans le même fichier les couple et, par extension, tout code qui dépend d'une ou plusieurs énumérations dépend de toutes les énumérations, que le code utilise réellement d'autres énumérations.

#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()préfère de beaucoup ne savoir que cela map_t, car sinon tout changement apporté aux autres l'affectera même s'il n'interagit pas réellement avec les autres.

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

void renderMap(map_t, mapIndex);

Cependant, dans le cas où les composants sont déjà couplés ensemble, fournir plusieurs énumérations dans un seul package peut facilement fournir une clarté et une simplicité supplémentaires, à condition qu'il y ait une raison logique claire pour les énumérations à coupler, que l'utilisation de ces énumérations est également couplée, et que leur fourniture n'introduit pas non plus de couplages supplémentaires.

#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);

Dans ce cas, il n'y a pas de lien direct et logique entre le type d'entité et le type de matériau (en supposant que les entités ne sont pas constituées d'un des matériaux définis). Si, cependant, nous avions un cas où, par exemple, une énumération dépend explicitement de l'autre, alors il est logique de fournir un seul package contenant toutes les énumérations couplées (ainsi que tout autre composant couplé), de sorte que le couplage puisse être isolé de ce paquet autant qu'il est raisonnablement possible.

// 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()];
}

Mais hélas ... même lorsque deux énumérations sont intrinsèquement couplées, même si c'est quelque chose d'aussi fort que "la deuxième énumération fournit des sous-catégories pour la première énumération", il peut arriver un moment où une seule énumération est nécessaire.

#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;
}

Par conséquent, puisque nous savons à la fois qu'il peut toujours y avoir des situations où la définition de plusieurs énumérations dans un seul fichier peut ajouter un couplage inutile, et que la fourniture d'énumérations couplées dans un seul package peut clarifier l'utilisation prévue et nous permettre d'isoler le code de couplage lui-même comme Dans la mesure du possible, la solution idéale consiste à définir chaque énumération séparément et à fournir des packages communs pour toutes les énumérations qui sont fréquemment utilisées ensemble. Les seules énumérations définies dans le même fichier seront celles qui sont intrinsèquement liées entre elles, de sorte que l'utilisation de l'une nécessite également l'utilisation de l'autre.

// 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 - Rétablir Monica
la source