¿Es una mala práctica incluir todas las enumeraciones en un archivo y usarlo en varias clases?

12

Soy un aspirante a desarrollador de juegos, trabajo en juegos independientes ocasionales, y durante un tiempo he estado haciendo algo que parecía una mala práctica al principio, pero realmente quiero obtener una respuesta de algunos programadores experimentados aquí.

Digamos que tengo un archivo llamado enumList.hdonde declaro todas las enumeraciones que quiero usar en mi juego:

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

La idea principal es declarar todas las enumeraciones en el juego en 1 archivo , y luego importar ese archivo cuando necesito usar una cierta enumeración de él, en lugar de declararlo en el archivo donde necesito usarlo. Hago esto porque hace las cosas limpias, puedo acceder a cada enumeración en 1 lugar en lugar de tener páginas abiertas solo para acceder a una enumeración.

¿Es esta una mala práctica y puede afectar el rendimiento de alguna manera?

Bugster
fuente
1
La estructura de origen no puede afectar el rendimiento: seguirá compilándose de la misma manera, sin importar dónde estén las enumeraciones. Entonces, realmente esta es una pregunta sobre dónde sería mejor colocarlos, y para enumeraciones pequeñas, un archivo no suena demasiado tabú.
Steffan Donal
Estaba preguntando en casos más extremos, un juego puede tener muchas enumeraciones y pueden ser bastante grandes, pero gracias por tu comentario
Bugster
44
No afectará el rendimiento de la aplicación, pero tendrá un efecto negativo en el tiempo de compilación. Por ejemplo, si agrega un material a los materials_tarchivos que no se ocupan de materiales, deberá reconstruirse.
Gort the Robot
14
es como poner todas las sillas de tu casa en la sala de sillas, así que si quieres sentarte, sabes a dónde ir.
Kevin Cline
Por otro lado, puede hacer ambas cosas fácilmente , colocando cada enumeración en su propio archivo y enumList.hsirviendo como una colección de #includes para esos archivos. Esto permite que los archivos que solo necesitan una enumeración lo obtengan directamente, al tiempo que proporciona un paquete singular para todo lo que realmente los quiere a todos.
Justin Time - Restablece a Monica el

Respuestas:

35

Realmente creo que es una mala práctica. Cuando medimos la calidad del código, hay algo que llamamos "granularidad". Su granularidad sufre severamente al poner todas esas enumeraciones en un solo archivo y, por lo tanto, la mantenibilidad también sufre.

Tendría un solo archivo por enumeración para encontrarlo rápidamente y agruparlo con el código de comportamiento de la funcionalidad específica (por ejemplo, enumeración de materiales en la carpeta donde está el comportamiento del material, etc.);

La idea principal es declarar todas las enumeraciones en el juego en 1 archivo, y luego importar ese archivo cuando necesito usar una cierta enumeración de él, en lugar de declararlo en el archivo donde necesito usarlo. Hago esto porque hace las cosas limpias, puedo acceder a cada enumeración en 1 lugar en lugar de tener páginas abiertas solo para acceder a una enumeración.

Puede pensar que está limpio, pero de hecho, no lo está. Acopla cosas que no se unen entre sí en cuanto a funcionalidad y módulo, y disminuye la modularidad de su aplicación. Dependiendo del tamaño de su base de código y de qué tan modular quiere que se estructura su código, esto podría convertirse en un problema mayor y en código / dependencias sucias en otras partes de su sistema. Sin embargo, si solo escribe un sistema pequeño y monolítico, esto no se aplica necesariamente. Sin embargo, no lo haría así incluso para un pequeño sistema monolítico.

Halcón
fuente
2
+1 por mencionar la granularidad, tomé en consideración las otras respuestas, pero tú haces un buen punto.
Bugster
+1 para archivo único por enumeración. por un lado, es más fácil de encontrar.
mauris
11
Sin mencionar que agregar un único valor de enumeración utilizado en un lugar hará que todos los archivos de todo su proyecto tengan que ser reconstruidos.
Gort the Robot
buen consejo. cambiaste de opinión de todos modos ... Siempre me gustó ir a CompanyNamespace.Enums ... y obtener una lista fácil, pero si la estructura del código es disciplinada, entonces tu enfoque es mejor
Matt Evans
21

Sí, es una mala práctica, no por el rendimiento sino por la capacidad de mantenimiento.

Hace que las cosas estén "limpias" solo en el modo OCD de "juntar cosas similares". Pero ese no es el tipo de "limpieza" útil y bueno.

Las entidades de código deben agruparse para maximizar la cohesión y minimizar el acoplamiento , lo que se logra mejor al agruparlas en módulos funcionales en gran medida independientes. Agruparlos por criterios técnicos (como juntar todas las enumeraciones) logra lo contrario: combina el código que no tiene absolutamente ninguna relación funcional y coloca enumeraciones que podrían usarse solo en un lugar en un archivo diferente.

Michael Borgwardt
fuente
3
Cierto; 'solo en el TOC "recolectan cosas similares juntas". 100 votos a favor si pudiera.
Dogweather
Ayudaría a editar la ortografía si pudiera, pero no cometiste suficientes errores para superar el umbral de "edición" de SE. :-P
Dogweather
Es interesante que casi todos los marcos web requieren que los archivos se recopilen por tipo en lugar de por función.
Kevin Cline
@kevincline: No diría "casi todos", solo aquellos que se basan en la convención sobre la configuración y, por lo general, también tienen un concepto de módulo que permite agrupar funcionalmente el código.
Michael Borgwardt
8

Bueno, esto son solo datos (no comportamiento). Lo peor que podría suceder posible / teóricamente es que se incluya y compile el mismo código más de una vez produciendo un programa relativamente más grande.

Es simplemente imposible que tales inclusiones puedan agregar más ciclos a sus operaciones, dado que no hay ningún código de comportamiento / procedimiento dentro de ellas (sin bucles, sin ifs, etc.).

Un programa (relativamente) más grande difícilmente puede afectar el rendimiento (velocidad de ejecución) y, en cualquier caso, es solo una preocupación teórica remota. La mayoría (tal vez todos) los compiladores administran incluyen de una manera que previene tales problemas.

En mi humilde opinión, las ventajas que obtienes con un solo archivo (código más legible y más manejable) superan en gran medida cualquier posible inconveniente.

AlexBottoni
fuente
55
Usted señala muy bien que creo que se ignoran todas las respuestas más populares: los datos inmutables no tienen ningún acoplamiento.
Michael Shaw
3
Supongo que nunca ha tenido que trabajar en proyectos que tomarían media hora o más para compilar. Si tiene todas sus enumeraciones en un archivo y cambia una enumeración, es hora de un largo descanso. Diablos, incluso si solo tomó un minuto, todavía es demasiado tiempo.
Dunk
2
Cualquier persona que trabaje en el módulo X bajo control de versiones debe obtener el módulo X y la enumeración cada vez que deseen trabajar. Además, me parece una forma de acoplamiento si, cada vez que cambia el archivo enum, su cambio afecta potencialmente a cada módulo de su proyecto. Si su proyecto es lo suficientemente grande como para que todos o la mayoría de los miembros del equipo no entiendan cada parte del proyecto, una enumeración global es bastante peligrosa. Una variable global inmutable ni siquiera es tan mala como una variable global mutable, pero aún así no es ideal. Dicho esto, una enumeración global probablemente esté bien en su mayoría en un equipo con <10 miembros.
Brian
3

Para mí, todo depende del alcance de su proyecto. Si hay un archivo con digamos 10 structs y esos son los únicos que se han usado, me sentiría perfectamente cómodo teniendo un archivo .h para él. Si tiene varios tipos diferentes de funcionalidad, digamos unidades, economía, edificios, etc. Definitivamente dividiría las cosas. Cree un units.h donde residen todas las estructuras que tratan con unidades. Si desea hacer algo con unidades en algún lugar, debe incluir unidades. Claro, pero también es un buen "identificador" de que algo con unidades probablemente se haga en este archivo.

Míralo de esta manera, no compras un supermercado porque necesitas una lata de coca cola;)

hoppa
fuente
3

No soy un desarrollador de C ++, por lo que esta respuesta será más genérica para OOA & D:

Típicamente, los objetos de código deben agruparse en términos de relevancia funcional, no necesariamente en términos de construcciones específicas del lenguaje. La pregunta clave sí-no que siempre se debe hacer es: "¿se espera que el codificador final use la mayoría o la totalidad de los objetos que obtiene al consumir una biblioteca?" En caso afirmativo, grupo de distancia. De lo contrario, considere dividir los objetos de código y colocarlos más cerca de otros objetos que los necesitan (aumentando así la posibilidad de que el consumidor necesite todo lo que realmente está accediendo).

El concepto básico es "alta cohesión"; los miembros del código (desde los métodos de una clase hasta las clases en un espacio de nombres o DLL, y las propias DLL) deben organizarse de manera que un codificador pueda incluir todo lo que necesitan y nada que no. Esto hace que el diseño general sea más tolerante al cambio; las cosas que deben cambiar pueden ser, sin afectar a otros objetos de código que no tuvieron que cambiar. En muchas circunstancias, también hace que la aplicación sea más eficiente en memoria; una DLL se carga en su totalidad en la memoria, independientemente de la cantidad de esas instrucciones que el proceso ejecute alguna vez. Por lo tanto, diseñar aplicaciones "esbeltas" requiere prestar atención a la cantidad de código que se extrae en la memoria.

Este concepto se aplica a prácticamente todos los niveles de organización del código, con diversos grados de impacto en la capacidad de mantenimiento, la eficiencia de la memoria, el rendimiento, la velocidad de creación, etc. Si un codificador necesita hacer referencia a un encabezado / DLL de tamaño bastante monolítico solo para acceder a un objeto, que no depende de ningún otro objeto en esa DLL, entonces la inclusión de ese objeto en la DLL probablemente debería repensarse. Sin embargo, también es posible ir demasiado lejos en la otra dirección; Una DLL para cada clase es una mala idea, ya que reduce la velocidad de compilación (más DLL para reconstruir con la sobrecarga asociada) y hace que el versionado sea una pesadilla.

Caso en cuestión: si cualquier uso del mundo real de su biblioteca de códigos implicaría usar la mayoría o todas las enumeraciones que está colocando en este único archivo "enumerations.h", entonces agrúpelas por todos los medios; sabrás dónde buscar para encontrarlos. Pero si su codificador consumidor pudiera necesitar solo una o dos de las docenas de enumeraciones que está proporcionando en el encabezado, entonces le animo a que las coloque en una biblioteca separada y la convierta en una dependencia de la más grande con el resto de las enumeraciones. . Eso permite a sus codificadores obtener solo el uno o dos que desean sin vincular a la DLL más monolítica.

KeithS
fuente
2

Este tipo de cosas se convierte en un problema cuando tienes múltiples desarrolladores trabajando en la misma base de código.

Ciertamente, he visto archivos globales similares que se convierten en un nexo para las colisiones de fusión y todo tipo de dolor en proyectos (más grandes).

Sin embargo, si usted es el único que trabaja en el proyecto, entonces debe hacer lo que le resulte más cómodo y solo adoptar las "mejores prácticas" si comprende (y está de acuerdo) con la motivación detrás de ellos.

Es mejor cometer algunos errores y aprender de ellos que arriesgarse a quedarse atrapado con las prácticas de programación de culto de carga por el resto de su vida.

William Payne
fuente
1

Sí, es una mala práctica hacerlo en un gran proyecto. BESO.

El joven compañero de trabajo cambió el nombre de una variable simple en un archivo .h central y 100 ingenieros esperaron 45 minutos para que se reconstruyeran todos sus archivos, lo que afectó el rendimiento de todos. ;)

Todos los proyectos comienzan pequeños, con el paso de los años, y maldecimos a quienes tomaron atajos tempranos para crear deuda técnica. Mejores prácticas, mantenga el contenido global .h limitado a lo que es necesariamente global.

AmyInNH
fuente
No está utilizando el sistema de revisión si alguien (joven o viejo, igual) puede (por diversión) hacer que todos reconstruyan el proyecto, ¿no es así?
Sanctus
1

En este caso, diría que la respuesta ideal es que depende de cómo se consuman las enumeraciones, pero que en la mayoría de las circunstancias probablemente sea mejor definir todas las enumeraciones por separado, pero si alguna de ellas ya está unida por diseño, debe proporcionar un medios de introducir dichas enumeraciones acopladas colectivamente. En efecto, tiene una tolerancia de acoplamiento hasta la cantidad de acoplamiento intencional ya presente, pero no más.

Teniendo esto en cuenta, es probable que la solución más flexible defina cada enumeración en un archivo separado, pero proporcione paquetes acoplados cuando sea razonable hacerlo (según lo determine el uso previsto de las enumeraciones involucradas).


La definición de todas sus enumeraciones en el mismo archivo las une y, por extensión, hace que cualquier código que dependa de una o más enumeraciones dependa de todas las enumeraciones, independientemente de si el código realmente utiliza alguna otra enumeración.

#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()preferiría saberlo map_t, porque de lo contrario, cualquier cambio en los demás lo afectará aunque en realidad no interactúe con los demás.

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

void renderMap(map_t, mapIndex);

Sin embargo, en el caso de que los componentes ya estén acoplados entre sí, proporcionar múltiples enumeraciones en un solo paquete puede proporcionar claridad y simplicidad adicionales, siempre que haya una razón lógica clara para que las enumeraciones se acoplen, que el uso de esas enumeraciones también se acople, y que proporcionarlos tampoco introduce acoplamientos adicionales.

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

En este caso, no existe una conexión directa y lógica entre el tipo de entidad y el tipo de material (suponiendo que las entidades no estén hechas de uno de los materiales definidos). Sin embargo, si tenemos un caso en el que, por ejemplo, una enumeración depende explícitamente de la otra, entonces tiene sentido proporcionar un solo paquete que contenga todas las enumeraciones acopladas (así como cualquier otro componente acoplado), de modo que el acoplamiento pueda ser aislado de ese paquete tanto como sea razonablemente posible.

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

Pero, por desgracia ... incluso cuando dos enumeraciones están intrínsecamente juntas, incluso si es algo tan fuerte como "la segunda enumeración proporciona subcategorías para la primera enumeración", todavía puede llegar un momento en que solo una de las enumeraciones sea necesaria.

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

Por lo tanto, dado que sabemos que siempre puede haber situaciones en las que definir enumeraciones múltiples en un solo archivo puede agregar un acoplamiento innecesario, y que proporcionar enumeraciones acopladas en un solo paquete puede aclarar el uso previsto y permitirnos aislar el código de acoplamiento real como En la medida de lo posible, la solución ideal es definir cada enumeración por separado y proporcionar paquetes conjuntos para cualquier enumeración que se pretenda utilizar con frecuencia. Las únicas enumeraciones definidas en el mismo archivo serán las que están intrínsecamente vinculadas entre sí, de modo que el uso de uno requiere el uso del otro también.

// 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 - Restablece a Monica
fuente