É uma prática ruim incluir todas as enumerações em um arquivo e usá-las em várias classes?

12

Eu sou um desenvolvedor de jogos aspirante, trabalho ocasionalmente em jogos independentes e, durante algum tempo, venho fazendo algo que parecia uma prática ruim no começo, mas eu realmente quero receber uma resposta de alguns programadores experientes aqui.

Digamos que eu tenho um arquivo chamado enumList.honde declaro todas as enumerações que quero usar no meu jogo:

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

A idéia principal é que declaro todas as enumerações do jogo em 1 arquivo e depois importo esse arquivo quando preciso usar uma determinada enumeração, em vez de declará-lo no arquivo em que preciso usá-lo. Eu faço isso porque isso torna as coisas limpas; eu posso acessar todas as enum em um lugar, em vez de ter páginas abertas apenas para acessar uma enum.

Essa é uma prática ruim e pode afetar o desempenho de alguma forma?

Bugster
fonte
1
A estrutura de origem não pode afetar o desempenho - ainda será compilada da mesma forma, não importa onde estejam as enumerações. Então, na verdade, essa é uma pergunta sobre onde seria melhor colocá-los e, para enumerações pequenas, um arquivo não parece muito tabu.
Steffan Donal
Eu estava perguntando em mais casos extremos, um jogo pode ter muitos enums e eles podem ser muito grande, mas obrigado pelo seu comentário
Bugster
4
Não afetará o desempenho do aplicativo, mas terá um efeito negativo no tempo de compilação. Por exemplo, se você adicionar um material a materials_tarquivos que não tratam de materiais, precisará ser reconstruído.
Gort the Robot
14
é como colocar todas as cadeiras da sua casa na sala da cadeira; se você quiser se sentar, sabe para onde ir.
Kevin cline
Como um aparte, você pode fazer as duas coisas facilmente , colocando cada enum em seu próprio arquivo e enumList.hservindo como uma coleção de #includes para esses arquivos. Isso permite que os arquivos que precisam apenas de uma enumeração o obtenham diretamente, enquanto fornecem um pacote singular para qualquer coisa que realmente queira todos eles.
Justin Time - Restabelece Monica

Respostas:

35

Eu realmente acho que é uma má prática. Quando medimos a qualidade do código, há algo que chamamos de "granularidade". Sua granularidade sofre severamente ao colocar todas essas enumerações em um único arquivo e, portanto, também sofre manutenção.

Eu teria um único arquivo por enum para encontrá-lo rapidamente e agrupá-lo com o código comportamental da funcionalidade específica (por exemplo, enum de materiais na pasta onde o comportamento dos materiais é etc.);

A idéia principal é que declaro todas as enumerações do jogo em 1 arquivo e depois importo esse arquivo quando preciso usar uma determinada enumeração, em vez de declará-lo no arquivo em que preciso usá-lo. Eu faço isso porque isso torna as coisas limpas; eu posso acessar todas as enum em um lugar, em vez de ter páginas abertas apenas para acessar uma enum.

Você pode pensar que é limpo, mas na verdade não é. Ele está acoplando itens que não pertencem juntos à funcionalidade e ao módulo e diminui a modularidade do seu aplicativo. Dependendo do tamanho da sua base de códigos e da modularidade que você deseja que seu código seja estruturado, isso pode evoluir para um problema maior e para códigos / dependências impuros em outras partes do seu sistema. No entanto, se você acabou de escrever um pequeno sistema monolítico, isso não se aplica necessariamente. No entanto, eu não faria isso mesmo em um pequeno sistema monolítico.

Falcão
fonte
2
+1 por mencionar a granularidade, tomei as outras respostas em consideração, mas você faz uma boa observação.
Bugster
+1 para arquivo único por enumeração. por um, é mais fácil de encontrar.
mauris
11
Sem mencionar que a adição de um único valor de enum usado em um local fará com que todos os arquivos em todo o projeto sejam reconstruídos.
Gort the Robot
Bom conselho. Você mudou a minha mente de qualquer maneira .. Eu sempre gostei CompanyNamespace.Enums vai .... e obter uma lista fácil, mas se a estrutura do código é disciplinado, então a sua abordagem é melhor
Matt Evans
21

Sim, é uma prática ruim, não por causa do desempenho, mas por causa da capacidade de manutenção.

Isso torna as coisas "limpas" apenas no TOC "coleciona coisas similares". Mas esse não é realmente o tipo útil e bom de "limpeza".

As entidades de código devem ser agrupadas para maximizar a coesão e minimizar o acoplamento , o que é melhor alcançado agrupando-as em módulos funcionais amplamente independentes. Agrupá-los por critérios técnicos (como reunir todas as enumerações) alcança o oposto - ele combina código que não tem absolutamente nenhuma relação funcional e coloca enumerações que podem ser usadas apenas em um local em um arquivo diferente.

Michael Borgwardt
fonte
3
Verdade; 'apenas no TOC "coletar coisas semelhantes juntos" caminho ". 100 votos positivos, se eu pudesse.
Dogweather
Ajudaria a editar a ortografia, se pudesse, mas você não cometeu erros suficientes para ultrapassar o limite de 'edição' do SE. :-P
Dogweather
interessante que quase todas as estruturas da web exijam que os arquivos sejam coletados por tipo e não por recurso.
Kevin Cline
@kevincline: eu não diria "quase todos" - apenas aqueles que são baseados em convenções sobre configuração e, normalmente, também possuem um conceito de módulo que permite agrupar funcionalmente o código.
Michael Borgwardt
8

Bem, isso são apenas dados (não comportamento). O pior que poderia / teoricamente acontecer é que o mesmo código seja incluído e compilado mais de uma vez produzindo um programa relativamente maior.

É simplesmente impossível que tais inclusões possam adicionar mais ciclos às suas operações, uma vez que não há nenhum código comportamental / processual dentro delas (sem loops, sem ifs etc.).

Um programa (relativamente) maior dificilmente pode afetar o desempenho (velocidade de execução) e, em qualquer caso, é apenas uma preocupação teórica remota. A maioria (talvez todos) dos compiladores gerenciam inclui de uma maneira que evite esses problemas.

IMHO, as vantagens que você obtém com um único arquivo (código mais legível e mais gerenciável) excedem em grande parte qualquer possível inconveniente.

AlexBottoni
fonte
5
Você faz uma observação muito boa que acho que todas as respostas mais populares estão ignorando - dados imutáveis ​​não têm nenhum acoplamento.
Michael Shaw
3
Eu acho que você nunca teve que trabalhar em projetos que levariam meia hora ou mais para serem compilados. Se você tiver todas as suas enumerações em um arquivo e alterar uma enumeração única, é hora de uma longa pausa. Caramba, mesmo que demorasse um minuto, ainda é muito longo.
Dunk
2
Qualquer pessoa que trabalhe no módulo X sob controle de versão deve obter o módulo X e a enumeração toda vez que desejar trabalhar. Além disso, parece-me uma forma de acoplamento se, toda vez que você altera o arquivo enum, sua alteração afeta potencialmente todos os módulos do seu projeto. Se o seu projeto for grande o suficiente para que todos ou a maioria dos membros da equipe não entendam todas as partes do projeto, uma enumeração global é bastante perigosa. Uma variável imutável global não é tão ruim quanto uma variável mutável global, mas ainda não é o ideal. Dito isto, um enum global provavelmente está bem em uma equipe com menos de 10 membros.
Brian
3

Para mim, tudo depende do escopo do seu projeto. Se houver um arquivo com, digamos, 10 estruturas e os únicos já usados, eu ficaria perfeitamente confortável em ter um arquivo .h. Se você tem vários tipos diferentes de funcionalidade, por exemplo, unidades, economia, edifícios, etc. Eu definitivamente dividiria as coisas. Crie um units.h onde residem todas as estruturas que lidam com as unidades. Se você quiser fazer algo com unidades em algum lugar, precisará incluir units.h, com certeza, mas também é um bom "identificador" que algo com unidades provavelmente seja feito neste arquivo.

Veja desta forma, você não compra um supermercado porque precisa de uma lata de coca-cola;)

hoppa
fonte
3

Como eu não sou um desenvolvedor de C ++, esta resposta será mais genérica para OOA & D:

Normalmente, os objetos de código devem ser agrupados em termos de relevância funcional, não necessariamente em termos de construções específicas da linguagem. A principal pergunta sim-não que sempre deve ser feita é: "será esperado que o codificador final use a maioria ou todos os objetos que obtém ao consumir uma biblioteca?" Se sim, agrupe-se. Caso contrário, considere dividir os objetos de código e colocá-los mais próximos de outros objetos que precisam deles (aumentando assim a chance de o consumidor precisar de tudo o que realmente está acessando).

O conceito básico é "alta coesão"; os membros do código (dos métodos de uma classe até as classes em um namespace ou DLL e as próprias DLLs) devem ser organizados de forma que um codificador possa incluir tudo o que precisa e nada que não seja. Isso torna o design geral mais tolerante a mudanças; coisas que precisam mudar podem ser, sem afetar outros objetos de código que não precisavam ser alterados. Em muitas circunstâncias, também torna o aplicativo mais eficiente em termos de memória; uma DLL é carregada na sua totalidade na memória, independentemente de quanto dessas instruções já são executadas pelo processo. A criação de aplicativos "enxutos" exige, portanto, que se preste atenção à quantidade de código extraída na memória.

Esse conceito se aplica a praticamente todos os níveis da organização do código, com diferentes graus de impacto na capacidade de manutenção, eficiência da memória, desempenho, velocidade de compilação etc. Se um codificador precisar fazer referência a um cabeçalho / DLL de tamanho bastante monolítico apenas para acessar um objeto, que não depende de nenhum outro objeto nessa DLL, a inclusão desse objeto na DLL provavelmente deve ser repensada. No entanto, é possível ir longe demais também; uma DLL para todas as classes é uma má ideia, pois diminui a velocidade de criação (mais DLLs são reconstruídas com sobrecarga associada) e torna o versionamento um pesadelo.

Caso em questão: se qualquer uso do mundo real da sua biblioteca de códigos envolver o uso da maioria ou de todas as enumerações que você está colocando nesse único arquivo "enumerations.h", então, por todos os meios, agrupe-as; você saberá onde procurar para encontrá-los. Mas se seu codificador consumidor puder precisar de apenas uma ou duas das dezenas de enumerações que você está fornecendo no cabeçalho, recomendamos que você as coloque em uma biblioteca separada e faça com que seja uma dependência da maior com o restante das enumerações . Isso permite que seus codificadores obtenham apenas o um ou dois que desejam, sem vincular à DLL mais monolítica.

KeithS
fonte
2

Esse tipo de coisa se torna um problema quando você tem vários desenvolvedores trabalhando na mesma base de código.

Certamente vi arquivos globais semelhantes se tornando um nexo para colisões de mesclagem e todo tipo de sofrimento em projetos (maiores).

No entanto, se você é o único que trabalha no projeto, deve fazer o que estiver mais à vontade e adotar as "melhores práticas" se entender (e concordar com) a motivação por trás deles.

É melhor cometer alguns erros e aprender com eles do que correr o risco de ficar preso às práticas de programação de cultos de carga pelo resto da sua vida.

William Payne
fonte
1

Sim, é uma prática ruim fazer isso em um grande projeto. BEIJO.

O colega de trabalho jovem renomeou uma variável simples em um arquivo .he principal e 100 engenheiros esperaram 45 minutos para que todos os seus arquivos fossem reconstruídos, o que afetou o desempenho de todos. ;)

Todos os projetos começam pequenos, com o passar dos anos, e amaldiçoamos aqueles que adotaram atalhos iniciais para criar dívida técnica. Práticas recomendadas, mantenha o conteúdo global .h limitado ao que é necessariamente global.

AmyInNH
fonte
Você não está usando o sistema de revisão se alguém (jovem ou velho, o mesmo) pode (por diversão) fazer todo mundo reconstruir o projeto, não é?
Sanctus 31/07
1

Nesse caso, eu diria que a resposta ideal é que depende de como as enumerações são consumidas, mas na maioria das circunstâncias provavelmente é melhor definir todas as enumerações separadamente, mas se alguma delas já estiver acoplada pelo design, você deverá fornecer um meios de introduzir as referidas enumerações acopladas coletivamente. Com efeito, você tem uma tolerância de acoplamento até a quantidade de acoplamento intencional já presente, mas não mais.

Considerando isso, é provável que a solução mais flexível defina cada enum em um arquivo separado, mas forneça pacotes acoplados quando for razoável fazê-lo (conforme determinado pelo uso pretendido das enumerações envolvidas).


Definir todas as suas enumerações no mesmo arquivo as une e, por extensão, faz com que qualquer código que depende de uma ou mais enumerações dependa de todas as enumerações, independentemente de o código realmente usar outras enumerações.

#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()preferiria apenas saber sobre isso map_t, porque, caso contrário, qualquer alteração nos outros o afetará, mesmo que na verdade não interaja com os outros.

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

void renderMap(map_t, mapIndex);

No entanto, no caso em que os componentes já estão acoplados, o fornecimento de várias enumerações em um único pacote pode facilmente fornecer clareza e simplicidade adicionais, desde que haja uma razão lógica clara para que as enumerações sejam acopladas, que o uso dessas enumerações também seja acoplado, e que fornecê-los também não introduz acoplamentos adicionais.

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

Nesse caso, não há conexão lógica direta entre o tipo de entidade e o tipo de material (supondo que as entidades não sejam feitas de um dos materiais definidos). Se, no entanto, tivermos um caso em que, por exemplo, um enum seja explicitamente dependente do outro, faz sentido fornecer um único pacote contendo todas as enumerações acopladas (assim como quaisquer outros componentes acoplados), para que o acoplamento possa ser isolado para esse pacote, tanto quanto for razoavelmente possível.

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

Mas, infelizmente ... mesmo quando duas enums estão intrinsecamente acopladas, mesmo que seja algo tão forte quanto "o segundo enum fornece subcategorias para o primeiro enum", ainda pode chegar um momento em que apenas um dos enums é necessário.

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

Portanto, como sabemos que sempre pode haver situações em que a definição de várias enumerações em um único arquivo pode adicionar acoplamento desnecessário e que o fornecimento de enumerações em um único pacote pode esclarecer o uso pretendido e nos permitir isolar o próprio código de acoplamento como Na medida do possível, a solução ideal é definir cada enumeração separadamente e fornecer pacotes conjuntos para quaisquer enumerações que se destinam a serem frequentemente usadas juntas. As únicas enumerações definidas no mesmo arquivo serão aquelas intrinsecamente ligadas, de modo que o uso de um também exija o uso do outro.

// 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 - Restabelecer Monica
fonte