È una cattiva pratica includere tutti gli enum in un file e usarlo in più classi?


12

Sono un aspirante sviluppatore di giochi, lavoro su giochi indie occasionali e per un po 'ho fatto qualcosa che all'inizio sembrava una cattiva pratica, ma voglio davvero ottenere una risposta da alcuni programmatori esperti qui.

Diciamo che ho un file chiamato in enumList.hcui dichiaro tutte le enumerazioni che voglio usare nel mio gioco:

// 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'idea principale è che dichiaro tutti gli enum nel gioco in 1 file , e quindi importa quel file quando devo usare un certo enum da esso, piuttosto che dichiararlo nel file dove devo usarlo. Faccio questo perché rende le cose pulite, posso accedere a ogni enum in 1 posto anziché avere pagine aperte solo per accedere a un enum.

È una cattiva pratica e può influire in qualche modo sulle prestazioni?


1
La struttura del sorgente non può influire sulle prestazioni: sarà comunque compilata allo stesso modo, indipendentemente da dove si trovino gli enum. Quindi davvero questa è una domanda su dove sarebbe meglio metterli, e per i piccoli enum, un file non suona troppo tabù.
Steffan Donal,

Stavo chiedendo in casi più estremi, un gioco può avere molti enum e possono essere piuttosto grandi, ma grazie per il tuo commento
Bugster

4
Non influirà sulle prestazioni dell'applicazione, ma avrà un effetto negativo sui tempi di compilazione. Ad esempio, se si aggiunge un materiale ai materials_tfile che non trattano materiali dovrà essere ricostruito.
Gort il robot il

14
è come mettere tutte le sedie di casa tua nella stanza delle sedie, quindi se vuoi sederti, sai dove andare.
Kevin Cline

Per inciso, puoi facilmente fare entrambe le cose , mettendo ogni enum nel suo file e enumList.hservendo come una raccolta di #includes per quei file. Ciò consente ai file che necessitano di un solo enum di ottenerlo direttamente, fornendo al contempo un pacchetto singolare per tutto ciò che li desidera davvero.
Justin Time - Ripristina Monica il

Risposte:


35

Penso davvero che sia una cattiva pratica. Quando misuriamo la qualità del codice, c'è qualcosa che chiamiamo "granularità". La tua granularità soffre gravemente mettendo tutti quegli enumerati in un singolo file e quindi anche la manutenibilità.

Avrei un singolo file per enum per trovarlo rapidamente e raggrupparlo con il codice comportamentale della funzionalità specifica (ad esempio enum dei materiali nella cartella in cui il comportamento dei materiali è ecc.);

L'idea principale è che dichiaro tutti gli enum nel gioco in 1 file, e quindi importa quel file quando devo usare un certo enum da esso, piuttosto che dichiararlo nel file dove devo usarlo. Faccio questo perché rende le cose pulite, posso accedere a ogni enum in 1 posto anziché avere pagine aperte solo per accedere a un enum.

Potresti pensare che sia pulito, ma in realtà non lo è. Abbina elementi che non appartengono alla funzionalità e al modulo e diminuisce la modularità dell'applicazione. A seconda delle dimensioni della tua base di codice e di quanto modulare desideri che il tuo codice sia strutturato, questo potrebbe evolversi in un problema più grande e codice / dipendenze impure in altre parti del tuo sistema. Tuttavia, se scrivi semplicemente un piccolo sistema monolitico, ciò non si applica necessariamente. Eppure, non lo farei così nemmeno per un piccolo sistema monolitico.


2
+1 per menzionare la granularità, ho preso in considerazione le altre risposte, ma hai ragione.
Bugster

+1 per singolo file per enum. per uno è più facile da trovare.
mauris,

11
Per non parlare del fatto che l'aggiunta di un singolo valore enum utilizzato in un unico punto comporterà la ricostruzione di tutti i file dell'intero progetto.
Gort il robot il

buon Consiglio. mi hai cambiato idea comunque .. Mi è sempre piaciuto andare a CompanyNamespace.Enums .... e ottenere un elenco facile, ma se la struttura del codice è disciplinata, allora il tuo approccio è migliore
Matt Evans

21

Sì, è una cattiva pratica, non a causa delle prestazioni ma a causa della manutenibilità.

Rende le cose "pulite" solo nel DOC "raccolgono cose simili insieme". Ma questo non è in realtà il tipo utile e buono di "pulizia".

Le entità di codice dovrebbero essere raggruppate per massimizzare la coesione e ridurre al minimo l' accoppiamento , il che si ottiene meglio raggruppandole in moduli funzionali ampiamente indipendenti. Raggruppandoli in base a criteri tecnici (come mettere insieme tutti gli enum) si ottiene il contrario: abbina il codice che non ha assolutamente alcuna relazione funzionale e inserisce enum che potrebbero essere usati solo in un posto in un altro file.


3
Vero; 'solo nel DOC "raccogliere cose simili insieme" in modo ". 100 voti se potessi.
Dogweather,

Se potessi, aiuterei a modificare l'ortografia, ma non hai commesso errori sufficienti per superare la soglia di modifica di SE. :-P
Dogweather,

interessante il fatto che quasi tutti i framework Web richiedono che i file vengano raccolti per tipo anziché per funzionalità.
Kevin Cline,

@kevincline: non direi "quasi tutti" - solo quelli che si basano sulla convenzione di over-configuration e, in genere, quelli hanno anche un concetto di modulo che consente di raggruppare il codice in modo funzionale.
Michael Borgwardt,

8

Bene, questi sono solo dati (non comportamento). Il peggio che potrebbe / teoricamente accadere è che lo stesso codice sia incluso e compilato più di una volta producendo un programma relativamente più grande.

È semplicemente impossibile che tali inclusioni possano aggiungere più cicli alle tue operazioni, dato che al loro interno non c'è alcun codice comportamentale / procedurale (nessun loop, nessun if, ecc.).

Un programma (relativamente) più grande può difficilmente influire sulle prestazioni (velocità di esecuzione) e, in ogni caso, è solo una preoccupazione teorica remota. La maggior parte (forse tutti) i compilatori gestiscono le inclusioni in modo da prevenire tali problemi.

IMHO, i vantaggi che si ottengono con un singolo file (codice più leggibile e più gestibile) superano ampiamente ogni possibile inconveniente.


5
Fai un ottimo punto sul fatto che penso che tutte le risposte più popolari stiano ignorando: i dati immutabili non hanno alcun accoppiamento.
Michael Shaw,

3
Immagino che non hai mai dovuto lavorare su progetti che avrebbero richiesto mezz'ora o più per essere compilati. Se hai tutti i tuoi enum in un file e cambi un singolo enum, è tempo di fare una lunga pausa. Cavolo, anche se ci è voluto solo un minuto, è ancora troppo lungo.
Dunk,

2
Chiunque lavori nel modulo X sotto il controllo della versione deve ottenere il modulo X e l'enum ogni volta che lo desiderano. Inoltre, mi sembra una forma di accoppiamento se, ogni volta che cambiate il file enum, la vostra modifica ha un potenziale effetto su ogni singolo modulo nel vostro progetto. Se il tuo progetto è abbastanza grande da impedire a tutti o alla maggior parte dei membri del team di comprendere ogni parte del progetto, un enum globale è piuttosto pericoloso. Una variabile immutabile globale non è nemmeno così grave come una variabile mutabile globale, ma non è ancora l'ideale. Detto questo, un enum globale è probabilmente per lo più ok in una squadra con <10 membri.
Brian,

3

Per me tutto dipende dall'ambito del tuo progetto. Se c'è un file con dire 10 strutture e che sono gli unici mai usati, mi farebbe comodo avere un file .h per esso. Se hai diversi tipi di funzionalità, per esempio unità, economia, edifici, ecc., Dividerei sicuramente le cose. Crea un units.h dove risiedono tutte le strutture che si occupano delle unità. Se vuoi fare qualcosa con le unità da qualche parte devi includere units.h certo, ma è anche un bel "identificatore" che qualcosa con le unità è probabilmente fatto in questo file.

Guardalo in questo modo, non acquisti un supermercato perché hai bisogno di una lattina di coca cola;)


3

Non sono uno sviluppatore C ++, quindi questa risposta sarà più generica per OOA e D:

In genere, gli oggetti di codice dovrebbero essere raggruppati in termini di rilevanza funzionale, non necessariamente in termini di costrutti specifici della lingua. La domanda sì-no chiave che dovrebbe sempre essere posta è "ci si aspetta che il codificatore finale utilizzi la maggior parte o tutti gli oggetti che ottengono quando consumano una libreria?" Se sì, allontanati. In caso contrario, considera la possibilità di suddividere gli oggetti del codice e di avvicinarli ad altri oggetti che ne hanno bisogno (aumentando così la possibilità che il consumatore abbia bisogno di tutto ciò a cui sta effettivamente accedendo).

Il concetto di base è "alta coesione"; i membri del codice (dai metodi di una classe fino alle classi in uno spazio dei nomi o DLL e le stesse DLL) dovrebbero essere organizzati in modo tale che un programmatore possa includere tutto ciò di cui ha bisogno e nulla che non lo faccia. Ciò rende il design generale più tollerante nei confronti del cambiamento; cose che devono cambiare possono essere, senza influenzare altri oggetti di codice che non hanno dovuto cambiare. In molte circostanze rende l'applicazione più efficiente in termini di memoria; una DLL viene caricata nella sua interezza nella memoria, indipendentemente da quante di queste istruzioni vengano mai eseguite dal processo. La progettazione di applicazioni "snelle" richiede pertanto attenzione alla quantità di codice che viene estratta nella memoria.

Questo concetto si applica praticamente a tutti i livelli dell'organizzazione del codice, con vari gradi di impatto su manutenibilità, efficienza della memoria, prestazioni, velocità di costruzione, ecc. Se un programmatore deve fare riferimento a un'intestazione / DLL di dimensioni piuttosto monolitiche solo per accedere a un oggetto, che non dipende da nessun altro oggetto in quella DLL, probabilmente l'inclusione di quell'oggetto nella DLL dovrebbe essere ripensata. Tuttavia è possibile andare troppo lontano anche dall'altra parte; una DLL per ogni classe è una cattiva idea, perché rallenta la velocità di compilazione (più DLL da ricostruire con overhead associato) e rende il versioning un incubo.

Caso in questione: se un qualsiasi utilizzo del mondo reale della tua libreria di codici implicherebbe l'uso della maggior parte o di tutte le enumerazioni che stai inserendo in questo singolo file "enumerations.h", allora raggruppale tutte insieme; saprai dove cercare per trovarli. Ma se il tuo programmatore consumatore potrebbe concepibilmente aver bisogno solo di una o due delle dozzine di enumerazioni che stai fornendo nell'intestazione, ti incoraggerei a metterle in una libreria separata e renderla una dipendenza di quella più grande con il resto delle enumerazioni . Ciò consente ai programmatori di ottenere solo uno o due desiderati senza collegarsi alla DLL più monolitica.


2

Questo genere di cose diventa un problema quando si hanno più sviluppatori che lavorano sulla stessa base di codice.

Ho sicuramente visto file globali simili diventare un nesso per unire collisioni e ogni sorta di dolore per progetti (più grandi).

Tuttavia, se sei l'unico che lavora al progetto, allora dovresti fare tutto ciò che ritieni più a tuo agio e adottare le "migliori pratiche" solo se capisci (e sei d'accordo) con la motivazione dietro di loro.

È meglio fare alcuni errori e imparare da essi piuttosto che rischiare di rimanere bloccati con le pratiche di programmazione di culto del carico per il resto della tua vita.


1

Sì, è una cattiva pratica farlo su un grande progetto. BACIO.

Il giovane collaboratore ha rinominato una semplice variabile in un file .h core e 100 ingegneri hanno aspettato 45 minuti per la ricostruzione di tutti i loro file, il che ha influito sulle prestazioni di tutti. ;)

Tutti i progetti iniziano in piccolo, si gonfiano nel corso degli anni e malediamo coloro che hanno preso le scorciatoie iniziali per creare debito tecnico. Le migliori pratiche mantengono i contenuti .h globali limitati a ciò che è necessariamente globale.


Non stai usando il sistema di revisione se qualcuno (giovane o vecchio, lo stesso) può semplicemente (per divertimento) far ricostruire a tutti il ​​progetto, vero?
Sanctus,

1

In questo caso, direi che la risposta ideale è che dipende da come vengono consumati gli enum, ma che nella maggior parte dei casi è probabilmente meglio definire tutti gli enum separatamente, ma se qualcuno di essi è già accoppiato in base alla progettazione, è necessario fornire un mezzi per introdurre collettivamente detti enum accoppiati. In effetti, hai una tolleranza di accoppiamento fino alla quantità di accoppiamento intenzionale già presente, ma non di più.

Considerando ciò, è probabile che la soluzione più flessibile definisca ogni enum in un file separato, ma fornisca pacchetti accoppiati quando è ragionevole farlo (come determinato dall'uso previsto degli enum coinvolti).


La definizione di tutte le enumerazioni nello stesso file le accoppia insieme e per estensione fa sì che qualsiasi codice che dipende da uno o più enum dipenda da tutti gli enum, indipendentemente dal fatto che il codice utilizzi effettivamente altri enum.

#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()preferirei solo conoscerlo map_t, perché altrimenti qualsiasi modifica agli altri lo influenzerà anche se in realtà non interagisce con gli altri.

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

void renderMap(map_t, mapIndex);

Tuttavia, nel caso in cui i componenti siano già accoppiati insieme, fornire più enum in un singolo pacchetto può facilmente fornire ulteriore chiarezza e semplicità, a condizione che vi sia una chiara ragione logica per cui gli enum possano essere accoppiati, che anche l'uso di quegli enum sia accoppiato, e che fornirli non introduce alcun accoppiamento aggiuntivo.

#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 questo caso, non esiste una connessione logica diretta tra il tipo di entità e il tipo di materiale (presupponendo che le entità non siano costituite da uno dei materiali definiti). Se, tuttavia, abbiamo avuto un caso in cui, ad esempio, un enum è esplicitamente dipendente dall'altro, allora ha senso fornire un singolo pacchetto contenente tutti gli enum accoppiati (così come qualsiasi altro componente accoppiato), in modo che l'accoppiamento possa essere isolato per quel pacchetto quanto è ragionevolmente possibile.

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

Ma ahimè ... anche quando due enum sono intrinsecamente accoppiati insieme, anche se è qualcosa di forte come "il secondo enum fornisce sottocategorie per il primo enum", potrebbe ancora venire un momento in cui è necessario solo uno degli enum.

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

Pertanto, poiché sappiamo sia che possono sempre esserci situazioni in cui la definizione di più enumerazioni in un singolo file può aggiungere un accoppiamento non necessario e che la fornitura di enumerazioni accoppiate in un singolo pacchetto può chiarire l'uso previsto e consentirci di isolare il codice di accoppiamento stesso come per quanto possibile, la soluzione ideale è definire ciascuna enumerazione separatamente e fornire pacchetti congiunti per tutti gli enum che sono destinati a essere usati frequentemente insieme. Gli unici enumerati nello stesso file saranno quelli intrinsecamente collegati tra loro, in modo tale che l'uso dell'uno richieda anche l'uso dell'altro.

// 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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.