Плохо ли включать все перечисления в один файл и использовать его в нескольких классах?


12

Я начинающий разработчик игр, я работаю над случайными инди-играми, и какое-то время я занимался чем-то, что поначалу казалось плохой практикой, но я действительно хочу получить ответ от некоторых опытных программистов здесь.

Допустим, у меня есть файл с именем, в enumList.hкотором я объявляю все перечисления, которые хочу использовать в своей игре:

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

Основная идея заключается в том, что я объявляю все перечисления в игре в 1 файле , а затем импортирую этот файл, когда мне нужно использовать определенное перечисление из него, а не объявляю его в файле, где мне нужно его использовать. Я делаю это, потому что это делает вещи чистыми, я могу получить доступ к каждому перечислению в 1 месте, а не открывать страницы исключительно для доступа к одному перечислению.

Это плохая практика и может ли она как-то повлиять на производительность?


1
Структура исходного кода не может влиять на производительность - она ​​все равно будет скомпилирована до того же уровня, независимо от того, где находятся перечисления. Так что на самом деле это вопрос о том, где их лучше всего поместить, и для небольших перечислений один файл не звучит слишком табу.
Штефан Донал

Я спрашивал в более крайних случаях, что в игре может быть много перечислений, и они могут быть довольно большими, но спасибо за ваш комментарий
Bugster

4
Это не повлияет на производительность приложения, но отрицательно скажется на времени компиляции. Например, если вы добавляете материал в materials_tфайлы, которые не имеют отношения к материалам, их придется перестраивать.
Gort the Robot

14
это все равно, что поставить все стулья в своем доме в комнате для стульев, поэтому, если вы хотите сесть, вы знаете, куда идти.
Кевин Клайн

Кроме того, вы можете легко сделать и то и другое , поместив каждое перечисление в свой собственный файл и enumList.hсоздавая коллекцию #includes для этих файлов. Это позволяет файлам, которым требуется только одно перечисление, получить его напрямую, при этом предоставляя единый пакет для всего, что действительно требует их всех.
Джастин Тайм - Восстановить Монику

Ответы:


35

Я действительно считаю, что это плохая практика. Когда мы измеряем качество кода, мы называем его гранулярностью. Ваша гранулярность сильно страдает, если поместить все эти перечисления в один файл, и, следовательно, ухудшается удобство сопровождения.

Я бы имел один файл на каждое перечисление, чтобы быстро найти его и сгруппировать с поведенческим кодом определенной функциональности (например, перечисление материалов в папке, где поведение материала и т. Д.);

Основная идея заключается в том, что я объявляю все перечисления в игре в 1 файле, а затем импортирую этот файл, когда мне нужно использовать определенное перечисление из него, а не объявляю его в файле, где мне нужно его использовать. Я делаю это, потому что это делает вещи чистыми, я могу получить доступ к каждому перечислению в 1 месте, а не открывать страницы исключительно для доступа к одному перечислению.

Вы можете подумать, что это чисто, но на самом деле это не так. Он объединяет вещи, которые не связаны друг с другом по функциональности и модульности, и уменьшает модульность вашего приложения. В зависимости от размера вашей кодовой базы и от того, насколько модульным вы хотите, чтобы ваш код был структурирован, это может перерасти в более серьезную проблему и нечистый код / ​​зависимости в других частях вашей системы. Однако, если вы просто пишете небольшую монолитную систему, это не обязательно применимо. Тем не менее, я бы не стал так делать даже для небольшой монолитной системы.


2
+1 за упоминание гранулярности, я принял во внимание другие ответы, но вы делаете хорошее замечание.
Багстер

+1 за один файл для каждого перечисления. для одного легче найти.
Mauris

11
Не говоря уже о том, что добавление единственного значения перечисления, используемого в одном месте, приведет к необходимости перестроения каждого файла в вашем проекте.
Gort the Robot

хороший совет. вы все равно передумали .. Мне всегда нравилось ходить в CompanyNamespace.Enums .... и получать простой список, но если структура кода дисциплинирована, то ваш подход лучше
Мэтт Эванс,

21

Да, это плохая практика не из-за производительности, а из-за ремонтопригодности.

Это делает вещи «чистыми» только в ОКР «собирать похожие вещи вместе» способом. Но это на самом деле не полезный и хороший вид «чистоты».

Сущности кода должны быть сгруппированы, чтобы максимизировать связность и минимизировать связывание , что лучше всего достигается путем группировки их в значительной степени независимые функциональные модули. Группировка их по техническим критериям (например, объединение всех перечислений) дает противоположное - объединяет код, который не имеет абсолютно никакой функциональной взаимосвязи, и помещает перечисления, которые могут использоваться только в одном месте, в другой файл.


3
Правда; «Только в ОКР« собирать похожие вещи вместе »способом». 100 голосов, если бы я мог.
Dogweather

Я бы помог изменить правописание, если бы мог, но вы не сделали достаточно ошибок, чтобы преодолеть порог SE 'edit' edit. :-P
Dogweather

Интересно, что почти все веб-фреймворки требуют, чтобы файлы собирались по типу, а не по функциям.
Кевин Клайн

@kevincline: Я бы не сказал «почти все» - только те, которые основаны на соглашении над конфигурацией, и, как правило, они также имеют концепцию модуля, которая позволяет группировать код функционально.
Майкл Боргвардт

8

Ну, это просто данные (а не поведение). Худшее, что могло бы / теоретически случиться, это то, что один и тот же код включен и скомпилирован более одного раза, создавая относительно большую программу.

Просто невозможно, чтобы такие включения могли добавить больше циклов к вашим операциям, учитывая, что внутри них нет никакого поведенческого / процедурного кода (без циклов, без if и т. Д.).

(Относительно) более крупная программа вряд ли может повлиять на производительность (скорость выполнения) и, в любом случае, является лишь теоретической проблемой. Большинство (может быть, все) компиляторы управляют включениями таким образом, чтобы предотвратить такие проблемы.

ИМХО, преимущества, которые вы получаете с одним файлом (более читаемый и более управляемый код), в значительной степени превосходят любые возможные недостатки.


5
Вы делаете очень хорошее замечание, что я думаю, что все более популярные ответы игнорируются - неизменные данные не имеют никакой связи вообще.
Майкл Шоу

3
Я полагаю, вам никогда не приходилось работать над проектами, на компиляцию которых уходило полчаса или больше. Если у вас есть все перечисления в одном файле и вы изменили одно перечисление, пришло время для длительного перерыва. Черт возьми, даже если это займет всего минуту, это все еще слишком долго.
Данк

2
Любой, кто работает в модуле X под управлением версией, должен получать модуль X и перечисление каждый раз, когда он хочет работать. Кроме того, это выглядит как форма связывания, если каждый раз, когда вы меняете файл enum, ваши изменения потенциально влияют на каждый отдельный модуль в вашем проекте. Если ваш проект достаточно велик, чтобы все или большинство членов команды не понимали каждый фрагмент проекта, глобальное перечисление довольно опасно. Глобальная неизменяемая переменная даже не так плоха, как глобальная изменяемая переменная, но все же она не идеальна. Тем не менее, глобальное перечисление, вероятно, в основном нормально в команде с <10 ​​членами.
Брайан

3

Для меня все зависит от масштаба вашего проекта. Если есть один файл, скажем, с 10 структурами, и это единственные из когда-либо использовавшихся файлов, мне было бы вполне удобно иметь для него один файл .h. Если у вас есть несколько разных типов функциональности, скажем, юниты, эконом, здания и т. Д., Я бы определенно разделил вещи. Создайте unit.h, где находятся все структуры, которые имеют дело с юнитами. Если вы хотите что-то сделать с блоками где-то, вам нужно обязательно включить unit.h, но это также хороший «идентификатор», что что-то с блоками, вероятно, сделано в этом файле.

Посмотрите на это так, вы не покупаете супермаркет, потому что вам нужна банка кока-колы;)


3

Я не разработчик C ++, поэтому этот ответ будет более общим для OOA & D:

Как правило, объекты кода должны быть сгруппированы с точки зрения функциональной релевантности, а не с точки зрения языковых конструкций. Ключевой вопрос «да-нет», который всегда следует задавать: «Ожидается ли, что конечный кодер будет использовать большинство или все объекты, которые они получают при использовании библиотеки?» Если да, сгруппируйтесь. Если нет, рассмотрите возможность разделения объектов кода и размещения их ближе к другим объектам, которые в них нуждаются (тем самым увеличивая вероятность того, что потребителю потребуется все, к чему он фактически обращается).

Основная концепция - «высокая сплоченность»; члены кода (от методов класса до классов в пространстве имен или DLL и самих DLL) должны быть организованы так, чтобы кодер мог включать в себя все, что им нужно, и ничего, что они не делают. Это делает общий дизайн более терпимым к изменениям; вещи, которые должны измениться, могут быть без влияния на другие объекты кода, которые не должны были изменяться. Во многих случаях это также делает приложение более эффективным с точки зрения памяти; DLL полностью загружается в память, независимо от того, сколько из этих инструкций когда-либо выполнялось процессом. Поэтому для разработки «простых» приложений необходимо обращать внимание на объем кода, помещаемого в память.

Эта концепция применима практически на всех уровнях организации кода, с различной степенью влияния на удобство обслуживания, эффективность использования памяти, производительность, скорость сборки и т. Д. Если кодировщик должен ссылаться на заголовок / DLL довольно монолитного размера только для доступа к одному объекту, который не зависит ни от какого другого объекта в этой DLL, тогда включение этого объекта в DLL, вероятно, следует пересмотреть. Однако можно пойти и другим путем; DLL для каждого класса - плохая идея, потому что она замедляет скорость сборки (больше библиотек DLL для перестройки с соответствующими издержками) и делает версионирование кошмаром.

Показательный пример: если любое реальное использование вашей библиотеки кода будет связано с использованием большинства или всех перечислений, которые вы помещаете в этот единственный файл "enumerations.h", то непременно сгруппируйте их вместе; Вы будете знать, где искать их. Но если вашему кодирующему потребителю может понадобиться только один или два из десятков перечислений, которые вы предоставляете в заголовке, я бы посоветовал вам поместить их в отдельную библиотеку и сделать ее зависимой от более крупной библиотеки с остальными перечислениями. , Это позволяет вашим кодировщикам получать только один или два, которые они хотят, без связи с более монолитной DLL.


2

Подобные вещи становятся проблемой, когда несколько разработчиков работают над одной и той же кодовой базой.

Я, конечно, видел, как похожие глобальные файлы становятся связующим звеном для коллизий слияния и всевозможных проблем в (более крупных) проектах.

Однако, если вы являетесь единственным, кто работает над проектом, тогда вы должны делать все, что вам больше всего удобно, и применять «лучшие практики», только если вы понимаете (и соглашаетесь с) мотивы, стоящие за ними.

Лучше сделать несколько ошибок и извлечь из них уроки, чем рискнуть застрять в практике программирования культовых грузов на всю оставшуюся жизнь.


1

Да, это плохая практика для большого проекта. ПОЦЕЛУЙ.

Молодой сотрудник переименовал простую переменную в основной файл .h, и 100 инженеров ждали 45 минут, пока все их файлы не будут восстановлены, что повлияло на производительность каждого. ;)

Все проекты начинаются с малого, с годами, и мы проклинаем тех, кто раньше использовал короткие пути для создания технического долга. Лучшие практики, ограничивайте глобальный контент .h тем, что обязательно является глобальным.


Вы не используете систему обзора, если кто-то (молодой или старый, тот же самый) может просто (без удовольствия) заставить всех перестроить проект, не так ли?
Святилище

1

В этом случае, я бы сказал, что идеальный ответ заключается в том, что это зависит от того, как используются перечисления, но в большинстве случаев, вероятно, лучше определить все перечисления по отдельности, но если какой-либо из них уже связан по проекту, вы должны предоставить средство совместного введения упомянутых связанных перечислений. По сути, у вас уже есть допуск на связывание до количества преднамеренного связывания, но не более.

Учитывая это, наиболее гибкое решение, скорее всего, определит каждое перечисление в отдельном файле, но предоставит связанные пакеты, когда это целесообразно сделать (что определяется предполагаемым использованием задействованных перечислений).


Определение всех ваших перечислений в одном и том же файле объединяет их вместе, и, соответственно, любой код, который зависит от одного или нескольких перечислений, зависит от всех перечислений, независимо от того, использует ли код какие-либо другие перечисления.

#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()Скорее всего, об этом знают map_t, потому что в противном случае любые изменения в других будут влиять на него, даже если он на самом деле не взаимодействует с другими.

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

void renderMap(map_t, mapIndex);

Однако в случае, когда компоненты уже связаны друг с другом, предоставление нескольких перечислений в одном пакете может легко обеспечить дополнительную ясность и простоту, при условии, что есть очевидная логическая причина для объединения перечислений, что использование этих перечислений также связано, и что их предоставление также не вводит никаких дополнительных соединений.

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

В этом случае нет прямой логической связи между типом объекта и типом материала (при условии, что объекты не сделаны из одного из определенных материалов). Однако, если у нас был случай, когда, например, одно перечисление явно зависит от другого, то имеет смысл предоставить единый пакет, содержащий все связанные перечисления (а также любые другие связанные компоненты), так что связь может быть изолированы от этой упаковки настолько, насколько это возможно.

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

Но, увы ... даже когда два перечисления по своей сути связаны друг с другом, даже если это что-то столь же сильное, как «второе перечисление предоставляет подкатегории для первого перечисления», может все же наступить момент, когда необходимо только одно из перечислений.

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

Следовательно, поскольку мы знаем и то, что всегда могут быть ситуации, когда определение нескольких перечислений в одном файле может добавить ненужную связь, и что предоставление связанных перечислений в одном пакете может прояснить предполагаемое использование и позволить нам изолировать сам фактический код связи как Насколько это возможно, идеальным решением является определение каждого перечисления отдельно и предоставление совместных пакетов для любых перечислений, которые предназначены для частого использования вместе. Единственными перечислениями, определенными в одном и том же файле, будут те, которые неразрывно связаны друг с другом, так что использование одного также требует использования другого.

// 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"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.