Чи погана практика включати всі перерахунки в один файл і використовувати їх у кількох класах?


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
Структура джерела не може вплинути на продуктивність - вона все одно буде складена до тієї самої, незалежно від того, де перераховуються. Тож справді це питання про те, куди було б найкраще їх розмістити, а для невеликих перерахунків один файл не звучить занадто табу.
Стефан Дональ

Я запитував у більш екстремальних випадках, гра може мати багато переживань, і вони можуть бути досить великими, але дякую за ваш коментар
Багстер

4
Це не вплине на продуктивність програми, але негативно вплине на час компіляції. Наприклад, якщо ви додаєте матеріал до materials_tфайлів, які не мають справи з матеріалами, доведеться перебудовувати.
Гурт Робота

14
це як би поставити всі крісла у вашому домі в крісло, тому якщо ви хочете сісти, ви знаєте, куди йти.
Кевін Клайн

Як сторону, ви можете легко зробити і те , і інше , помістивши кожен перелік у власний файл і enumList.hпослуживши колекцією #includes для цих файлів. Це дозволяє файлам, яким потрібен лише один перерахунок, щоб отримати його безпосередньо, надаючи єдиний пакет для всього, що дійсно хоче всіх.
Час Джастіна -

Відповіді:


35

Я дійсно думаю, що це погана практика. Коли ми вимірюємо якість коду, є щось, що ми називаємо «деталізація». Ваша деталізація сильно страждає, помістивши всі ці перерахунки в один файл, і, таким чином, також зазнає ремонтопридатності.

Я мав би один файл за перерахуванням, щоб швидко знайти його і згрупувати його за поведінковим кодом конкретної функціональності (наприклад, матеріали enum у папці, де поведінка матеріалу тощо);

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

Ви можете подумати, що це чисто, але насправді це не так. Це з'єднання речей, які не належать до функціональності та модулів, і зменшує модульність вашої програми. Залежно від розміру вашої кодової бази та того, наскільки модульним ви хочете структурувати ваш код, це може перерости у більшу проблему та нечисті коди / залежності в інших частинах вашої системи. Однак якщо ви просто пишете невелику, монолітну систему, це не обов'язково стосується. І все-таки я б не робив цього так навіть для невеликої монолітної системи.


2
+1 для згадування детальності, я взяв до уваги інші відповіді, але ви добре зазначаєте.
Багстер

+1 за один файл на перерахунок. для одного це легше знайти.
mauris

11
Не кажучи вже про те, що додавання єдиного значення enum, яке використовується в одному місці, призведе до того, що кожен файл у вашому проекті повинен бути перебудований.
Гурт Робота

хороша порада. Ви все-таки передумали .. Мені завжди подобалось їхати CompanyNamespace.Enums .... і отримувати простий список, але якщо структура коду буде дисциплінованою, то ваш підхід краще
Метт Еванс

21

Так, це погана практика не через продуктивність, а через ремонтопридатність.

Це робить речі "чистими" лише в OCD "збирати подібні речі разом" способом. Але це насправді не корисний і хороший вид «чистоти».

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


3
Правда; "тільки в OCD" збирають подібні речі разом "способом". 100 грошей, якщо я міг.
Dogweather

Я б допомогла відредагувати написання, якщо могла, але ви не зробили достатньо помилок, щоб перейти поріг SE «редагувати». :-P
Dogweather

цікаво, що майже всі веб-рамки вимагають збирання файлів за типом, а не за функціями.
Кевін Клайн

@kevincline: Я б не сказав "майже всі" - лише ті, що базуються на конфігурації конвенцій, і, як правило, вони також мають модульну концепцію, яка дозволяє групувати код функціонально.
Майкл Боргвардт

8

Ну, це лише дані (а не поведінка). Найгірше, що могло / теоретично могло статися, це те, що той самий код включається та компілюється не раз, створюючи порівняно більшу програму.

Це просто неможливо, що такі включення можуть додати більше циклів до ваших операцій, враховуючи, що всередині них немає жодного поведінкового / процедурного коду (немає циклів, немає ifs тощо).

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

IMHO, переваги, які ви отримуєте за допомогою одного файлу (більш читабельний та більш керований код), значною мірою перевищують будь-який можливий недолік.


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

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

2
Кожен, хто працює в модулі X під контролем версій, повинен отримувати модуль X та enum кожного разу, коли вони хочуть виконувати роботу. Далі, це вражає мене як форму з’єднання, якщо щоразу, коли ви змінюєте файл enum, ваша зміна потенційно впливає на кожен модуль вашого проекту. Якщо ваш проект досить великий, що всі або більшість членів команди не розуміють кожного фрагменту проекту, глобальний перелік є досить небезпечним. Глобальна незмінна змінна навіть не настільки ж погана, як глобальна змінна змінна, але вона все ще не ідеальна. Але, мабуть, глобальний перелік, мабуть, здебільшого добре в команді з <10 членами.
Брайан

3

Для мене все залежить від обсягу вашого проекту. Якщо є один файл з кажуть 10 структур, і це єдині, що коли-небудь використовуються, мені буде дуже зручно мати один .h файл для нього. Якщо у вас є кілька різних типів функціональності, скажімо, одиниці, економіка, будівлі тощо, я б точно розбив речі. Створіть units.h, де перебувають усі структури, які мають справу з одиницями. Якщо ви хочете щось зробити з одиницями, вам потрібно включити Unit.h, але це також приємний "ідентифікатор", що щось з одиницями, ймовірно, робиться в цьому файлі.

Подивіться на це так, ви не купуєте супермаркет, тому що вам потрібна банка коксу;)


3

Я не розробник C ++, тому ця відповідь буде більш загальною для OOA & D:

Як правило, об'єкти коду слід групувати за функціональною актуальністю, не обов'язково з точки зору конструкцій, що залежать від мови. Ключовим питанням "так-ні", яке завжди слід задавати, є "чи очікується, що кінцевий кодер буде використовувати більшість або всі об'єкти, які вони отримують під час використання бібліотеки?" Якщо так, групуйте подалі. Якщо ні, подумайте про розділення об'єктів коду та розміщення їх ближче до інших необхідних їм об'єктів (тим самим збільшуючи ймовірність того, що споживачеві знадобиться все, до чого вони фактично мають доступ).

Основне поняття - "висока згуртованість"; члени коду (від методів класу аж до класів у просторі імен або DLL та самих DLL) повинні бути організовані таким чином, що кодер може включати в себе все необхідне, і нічого у них немає. Це робить загальний дизайн більш толерантним до змін; речі, які повинні змінитися, можуть бути, не впливаючи на інші об'єкти коду, які не довелося змінювати. За багатьох обставин це також робить додаток більш ефективним для пам'яті; DLL повністю завантажується в пам'ять, незалежно від того, скільки цих інструкцій виконує процес. Таким чином, проектування "худорлявих" додатків вимагає звернути увагу на те, скільки код втягується в пам'ять.

Ця концепція застосовується практично на всіх рівнях організації коду, з різним ступенем впливу на ремонтопридатність, ефективність пам’яті, продуктивність, швидкість збирання тощо. Якщо кодеру потрібно звернутися до заголовка / DLL досить монолітного розміру просто для доступу до одного об’єкта, який не залежить від будь-якого іншого об'єкта в цій DLL, то включення цього об’єкта до DLL, мабуть, слід переосмислити. Однак можна зайти занадто далеко і іншим шляхом; DLL для кожного класу є поганою ідеєю, оскільки він уповільнює швидкість збирання (більше DLL для відновлення з відповідними накладними витратами) і робить версію кошмаром.

Справа в суті: якщо будь-яке використання вашої бібліотеки кодів у реальному світі передбачає використання більшості чи всіх перелічень, які ви вводите в цей єдиний файл "enumerations.h", то будь-якими способами згрупуйте їх разом; ти будеш знати, де їх шукати. Але якщо вашому споживачу, що споживає, можливо, знадобиться лише одне чи два з десятка перелічень, які ви надаєте у заголовку, то я б закликав вас помістити їх в окрему бібліотеку і зробити її залежною від більшої з рештою перерахувань . Це дозволяє вашим кодерам отримати лише те, що вони хочуть, без посилання на більш монолітну DLL.


2

Така річ стає проблемою, коли у вас є кілька розробників, які працюють на одній базі коду.

Я, звичайно, бачив, як подібні глобальні файли стають прив'язкою до зіткнень злиття та всілякого горя по (більшим) проектам.

Однак якщо ви єдиний, хто працює над проектом, тоді вам слід робити все, що вам найбільше подобається, і лише приймати «найкращі практики», якщо ви розумієте (і погоджуєтесь) мотивацію, що стоїть за ними.

Краще зробити деякі помилки і навчитися на них, ніж ризикувати зациклюватися на практиці програмування культових культів на все життя.


1

Так, це є поганою практикою робити це у великому проекті. KISS.

Молодий колега перейменував просту змінну в основний .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.