すべての列挙型を1つのファイルに含め、複数のクラスで使用するのは悪い習慣ですか?


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か所ですべての列挙にアクセスできます。

これは悪い習慣ですか、何らかの形でパフォーマンスに影響を与えることができますか?


1
ソース構造はパフォーマンスに影響を与えることはできません-列挙がどこにあっても同じようにコンパイルされます。本当にこれは、それらをどこに配置するのが最善かについての質問であり、小さな列挙型の場合、1つのファイルがタブーに聞こえることはありません。
ステファンドナル

私はもっと極端なケースで求めていた、ゲームは多くの列挙型を持つことができ、彼らはかなり大きなものとなりますが、あなたのコメントに感謝
Bugster

4
アプリケーションのパフォーマンスには影響しませんが、コンパイル時間に悪影響を及ぼします。たとえば、マテリアルをmaterials_t処理しないファイルにマテリアルを追加する場合、リビルドする必要があります。
ロボットを取得

14
それはまるであなたの家のすべての椅子を椅子の部屋に置くようなものです。
ケビンクライン

余談ですが、各enumを独自のファイルに入れ、それらのファイルのsのコレクションとして機能させることで、両方を簡単に行うことができます。これにより、1つのenumのみを必要とするファイルが直接取得できますが、実際にすべてを必要とするものに対しては単一のパッケージを提供します。enumList.h#include
ジャスティンタイム-モニカの復活

回答:


35

私は本当に悪い習慣だと思います。コードの品質を測定するとき、「粒度」と呼ばれるものがあります。これらの列挙型をすべて1つのファイルに入れると、粒度が著しく低下し、保守性も低下します。

列挙ごとに1つのファイルを用意して、それをすばやく見つけ、特定の機能の動作コードでグループ化します(たとえば、材料の動作があるフォルダ内の材料の列挙など)。

主なアイデアは、ゲーム内のすべての列挙型を1つのファイルで宣言し、使用する必要があるファイルで宣言するのではなく、特定の列挙型を使用する必要があるときにそのファイルをインポートすることです。これを行うのは、物事がきれいになるからです。1つの列挙にアクセスするためだけにページを開くのではなく、1か所ですべての列挙にアクセスできます。

あなたはそれがきれいだと思うかもしれませんが、実際にはそうではありません。機能的およびモジュール的に一緒になっていないものを結合し、アプリケーションのモジュール性を低下させます。コードベースのサイズと、コードをどの程度モジュール化するかによって、これはシステムの他の部分のより大きな問題とコード/依存関係の汚れに発展する可能性があります。ただし、小さなモノリシックシステムを記述するだけの場合、これは必ずしも当てはまりません。しかし、小さなモノリシックシステムであっても、このようなことはしません。


2
粒度に言及するための+1、私は他の答えを考慮に入れましたが、あなたは良い点を述べています。
バグスター

列挙ごとに1つのファイルに対して+1。一つは見つけやすいです。
モーリス

11
1つの場所で使用される単一の列挙値を追加すると、プロジェクト全体のすべてのファイルを再構築する必要があることは言うまでもありません。
ロボットを取得

いいアドバイス。あなたは、コードの構造が規律されている場合、その後、あなたのアプローチが優れている私はいつもCompanyNamespace.Enums行く気に入って....と簡単なリストを取得する。..とにかく私の心を変更しましたが、
マット・エバンス

21

はい、パフォーマンスのためではなく、保守性のために、それは悪い習慣です。

OCDの「類似したものを一緒に収集する」方法でのみ物を「きれい」にします。しかし、それは実際には便利で良い種類の「清潔さ」ではありません

コードエンティティは最大化するためにグループ化されるべき結束をし、最小限のカップリング最高の大部分が独立した機能モジュールにそれらをグループ化することによって達成されます。それらを技術的な基準(すべての列挙型をまとめるなど)でグループ化すると、逆になります。機能的な関係がまったくないコードを結合し、1つの場所でのみ使用される列挙型を別のファイルに入れます。


3
本当; 「OCDでのみ「類似したものを一緒に収集する」方法」。できれば100の賛成票。
ドッグウェザー14年

できればスペルの編集を手伝いますが、SEの「編集」のしきい値を超えるための十分なエラーはありませんでした。:-P-ドッグウェザー14
1

興味深いことに、ほとんどすべてのWebフレームワークでは、機能ではなくタイプごとにファイルを収集する必要があります。
ケビンクライン

@kevincline:「ほぼすべて」とは言いません。コンベンションオーバーコンフィギュレーションに基づいているものだけで、通常は、コードを機能的にグループ化できるモジュールコンセプトもあります。
マイケルボルグワード

8

まあ、これは単なるデータです(動作ではありません)。おそらく/理論的に発生する可能性のある最悪の事態は、同じコードが含まれており、比較的大きなプログラムを作成して複数回コンパイルされていることです。

そのようなインクルードが動作内にサイクルを追加する可能性があることは、まったく不可能です。その中には、動作/手続きコードがありません(ループやifがないなど)。

(比較的)大規模なプログラムは、パフォーマンス(実行速度)にほとんど影響を与えることはなく、いずれにせよ、理論上の問題にすぎません。ほとんどの(おそらくすべての)コンパイラは、このような問題を防ぐ方法でインクルードを管理します。

私見、単一のファイルで得られる利点(読みやすく管理しやすいコード)は、考えられる欠点を大きく上回ります。


5
あなたは非常に良い点を指摘します。私は、より一般的な答えはすべて無視していると思います。不変のデータにはまったくカップリングがありません。
マイケルショー

3
コンパイルに30分以上かかるプロジェクトに取り組む必要はなかったと思います。1つのファイルにすべての列挙型があり、単一の列挙型を変更したら、長い休憩の時間です。ちょっと、たとえそれがほんの少しかかったとしても、それはまだ長すぎます。
ダンク

2
バージョン管理下のモジュールXで作業している人は、作業を行うたびにモジュールXと列挙を取得する必要があります。さらに、列挙ファイルを変更するたびに、その変更がプロジェクト内のすべてのモジュールに影響を与える可能性がある場合、それはカップリングの形として私を襲います。プロジェクトが十分に大きく、すべてまたはほとんどのチームメンバーがプロジェクトのすべての部分を理解できない場合、グローバル列挙型はかなり危険です。グローバル不変変数は、グローバル可変変数ほど悪くはありませんが、それでも理想的ではありません。とはいえ、グローバル列挙型は、おそらく10人未満のメンバーのチームで大丈夫でしょう。
ブライアン

3

私にとってそれはすべてあなたのプロジェクトの範囲に依存します。たとえば、10個の構造体を持つファイルが1つだけあり、それがこれまでに使用された唯一のファイルである場合、1つの.hファイルがあれば十分に満足できます。ユニット、経済、建物など、いくつかの異なるタイプの機能がある場合、私は間違いなく物事を分割します。unitsを扱うすべての構造体が存在するunits.hを作成します。ユニットで何かをしたい場合は、units.hをインクルードする必要がありますが、ユニットで何かがおそらくこのファイルで行われるのは素晴らしい「識別子」です。

このように見てください、あなたはコークスの缶を必要とするので、スーパーマーケットを買わない;)


3

私はC ++開発者ではないので、この答えはOOA&Dにより一般的です:

通常、コードオブジェクトは機能的な関連性の観点からグループ化する必要があり、言語固有の構成要素の観点からグループ化する必要はありません。常に問われるべき重要なyes-noの質問は、「エンドコーダーは、ライブラリを使用するときに取得するオブジェクトのほとんどまたはすべてを使用することが期待されますか?」です。はいの場合、グループ化してください。そうでない場合は、コードオブジェクトを分割し、それらを必要とする他のオブジェクトの近くに配置することを検討してください(それにより、消費者が実際にアクセスしているすべてを必要とする可能性が高まります)。

基本的な概念は「高粘着性」です。コードメンバー(クラスのメソッドから名前空間またはDLL内のクラス、およびDLL自体まで)は、コーダーが必要なものをすべて含めることができ、不要なものは含めないように編成する必要があります。これにより、設計全体の変更に対する耐性が高まります。変更する必要があるものは、変更する必要のない他のコードオブジェクトに影響を与えることなく可能です。多くの状況では、アプリケーションのメモリ効率も向上します。DLLは、プロセスによって実行される命令の量に関係なく、メモリ全体にロードされます。したがって、「無駄のない」アプリケーションを設計するには、メモリに引き込まれるコードの量に注意を払う必要があります。

この概念は、保守性、メモリ効率、パフォーマンス、ビルド速度などにさまざまな影響を与えながら、実質的にすべてのレベルのコード編成に適用されます。そのDLLの他のオブジェクトに依存しないため、そのオブジェクトをDLLに含めることはおそらく考え直すべきです。ただし、逆に行き過ぎることも可能です。すべてのクラスのDLLは、ビルド速度が低下し(関連するオーバーヘッドで再構築するDLLが増える)、バージョニングが悪夢となるため、悪い考えです。

適切なケース:コードライブラリの実際の使用で、この単一の "enumerations.h"ファイルに入れている列挙のほとんどまたはすべてを使用する必要がある場合は、必ずそれらをグループ化します。あなたはそれらを見つけるためにどこを探すべきかを知っているでしょう。しかし、消費するコーダーがヘッダーで提供する数十の列挙のうちの1つまたは2つだけを必要とする可能性がある場合は、それらを別のライブラリに入れ、残りの列挙の大きいライブラリの依存関係にすることをお勧めします。これにより、コーダーは、よりモノリシックなDLLにリンクせずに、必要な1つまたは2つだけを取得できます。


2

複数の開発者が同じコードベースで作業している場合、この種のことが問題になります。

私は確かに、同様のグローバルファイルがマージ衝突のネクサスになり、(より大きな)プロジェクトでのあらゆる種類の悲しみを目にしました。

ただし、プロジェクトで作業しているのが自分だけである場合は、最も快適なことは何でも行い、その背後にある動機を理解(および同意)する場合にのみ「ベストプラクティス」を採用する必要があります。

あなたの人生の残りの部分で貨物カルトのプログラミング慣行にこだわるリスクよりも、いくつかの間違いを犯し、そこから学ぶ方が良いです。


1

はい、大きなプロジェクトでそうするのは悪い習慣です。接吻。

若い同僚は、コア.hファイルの単純な変数の名前を変更し、100人のエンジニアがすべてのファイルが再構築されるまで45分待機しました。これは全員のパフォーマンスに影響しました。;)

すべてのプロジェクトは、長年にわたって小さなバルーンで始まり、技術的な負債を生み出すための早期のショートカットを取った人々を呪います。ベストプラクティスは、グローバルな.hコンテンツを必然的にグローバルなものに制限することです。


誰か(若いか古いか、同じ)が(楽しみから)誰もがプロジェクトを再構築できる場合、レビューシステムを使用していませんか?
サンクタス

1

この場合、理想的な答えは列挙型の消費方法に依存することですが、ほとんどの場合、すべての列挙型を個別に定義するのがおそらく最善ですが、それらのいずれかが既に設計によって結合されている場合は、提供する必要があります結合列挙をまとめて導入する手段。実際には、すでに存在する意図的なカップリングの量までのカップリング許容差がありますが、それ以上はありません。

このことを考慮すると、最も柔軟なソリューションは、別のファイルに各列挙型を定義し、する可能性があるが、(関与列挙型の使用目的によって決定される)それはそうするのが妥当だ時に結合されたパッケージを提供しています。


すべての列挙を同じファイルで定義すると、それらが結合され、拡張により、コードが実際に他の列挙を使用するかどうかに関係なく、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);

この場合、エンティティタイプとマテリアルタイプの間に直接の論理的な接続はありません(エンティティが定義されたマテリアルのいずれかで作成されていないことを前提としています)。ただし、たとえば、1つの列挙が他の列挙に明示的に依存している場合、すべての結合列挙(および他の結合コンポーネント)を含む単一のパッケージを提供するのが理にかなっています。合理的に可能な限りそのパッケージに隔離されます。

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

しかし、悲しいかな... 2つの列挙型が本質的に一緒に結合されている場合でも、「2番目の列挙型が最初の列挙型のサブカテゴリを提供する」ほど強力な場合でも、1つだけの列挙型が必要な場合があります。

#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"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.