将所有枚举包含在一个文件中并在多个类中使用它是一种不好的做法吗?


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
源结构不会影响性能-无论枚举在哪里,它都将被编译为相同的结构。因此,实际上这是一个关于最好将它们放在哪里的问题,对于小枚举,一个文件听起来不太忌讳。
Steffan Donal

我问的是在更极端的情况下,一个游戏可以包含很多枚举,并且它们可能很大,但是感谢您的评论
Bugster 2012年

4
它不会影响应用程序性能,但是会对编译时间产生负面影响。例如,如果将材料添加到materials_t不处理材料的文件中,则必须重新构建。

14
这就像将所有椅子放在您房间的椅子上一样,所以如果您想坐下,就知道要去哪里。
凯文·克莱恩

顺便说一句,您可以通过将每个枚举放在自己的文件中并充当这些文件的s 集合来轻松地完成这两个操作。这样一来,只需要一个枚举即可直接获取文件的文件,同时为真正需要它们的任何文件提供单个包。enumList.h#include
贾斯汀时间-恢复莫妮卡

Answers:


35

我真的认为这是一个坏习惯。当我们测量代码质量时,我们称之为“粒度”。通过将所有这些枚举放在一个文件中,您的粒度将受到严重影响,因此可维护性也会受到影响。

每个枚举只有一个文件,可以快速找到它并与特定功能的行为代码进行分组(例如,材料行为所在的文件夹中的材料枚举等);

主要思想是,我在一个文件中声明了游戏中的所有枚举,然后在需要使用某个枚举时将其导入,而不是在需要使用该文件的地方声明了该枚举。我这样做是因为它使事情变得干净,我可以在一个地方访问每个枚举,而不必只打开页面来访问一个枚举。

您可能会认为它很干净,但实际上并非如此。它把不属于功能性和模块性的事物耦合在一起,从而降低了应用程序的模块化程度。根据代码库的大小以及您希望代码的模块化程度,这可能会导致更大的问题以及系统其他部分的代码/依赖关系不明确。但是,如果只编写一个小型的整体系统,则不一定适用。但是,即使对于小型整体系统,我也不会这样做。


2
+1表示粒度,我考虑了其他答案,但您的观点很不错。
Bugster 2012年

每个枚举为单个文件+1。一方面,它更容易找到。
毛里求斯2012年

11
更不用说在一个地方添加单个枚举值会导致整个项目中的每个文件都必须重建。

好建议。您还是改变了主意。.我一直喜欢去CompanyNamespace.Enums ....并获得简单的清单,但是如果对代码结构进行了规范,那么您的方法会更好
Matt Evans

21

是的,这不是一个好习惯,不是因为性能而是因为可维护性。

它使事物“干净”只有在OCD中“一起收集相似的事物”的方式。但这实际上不是有用且良好的“清洁度”。

应将代码实体组合在一起,以最大程度地提高内聚性,并最大程度地减少耦合,这最好通过将它们分为很大程度上独立的功能模块来实现。按技术标准将它们分组(例如将所有枚举放在一起)可以达到相反的效果-它将绝对没有功能关系的代码耦合在一起,并将只能在一个地方使用的枚举放到另一个文件中。


3
真正; “只有在强迫症中“以类似方式收集类似的东西”的方式”。如果可以的话,可以投票100次。
Dogweather 2014年

如果可以的话,我会帮助编辑拼写,但是您没有犯足够多的错误来超过SE“编辑”阈值。:-P
Dogweather 2014年

有趣的是,几乎所有的Web框架都要求按类型而不是按功能收集文件。
凯文·克莱恩

@kevincline:我不会说“几乎所有”,只是那些基于配置约定的,通常,它们也具有允许对功能进行代码分组的模块概念。
Michael Borgwardt

8

好吧,这只是数据(不是行为)。在理论上可能发生的最坏情况是,包含相同的代码并对其进行多次编译会产生相对较大的程序。

假设其中没有任何行为/过程代码(没有循环,没有ifs等),那么这样的包含几乎不可能为您的操作增加更多的周期。

(相对)较大的程序几乎不会影响性能(执行速度),并且在任何情况下都只是理论上的一个小问题。大多数(也许所有)编译器都以防止此类问题的方式来管理包含内容。

恕我直言,使用单个文件(更具可读性和更易管理的代码)的好处大大超过了任何可能的缺点。


5
您提出了一个很好的观点,我认为所有更流行的答案都被忽略了-不可变数据根本没有任何耦合。
迈克尔·肖

3
我猜您从未需要进行需要半小时或更长时间才能编译的项目。如果将所有枚举都放在一个文件中并更改一个枚举,那么该是一个漫长的休息时间了。哎呀,即使只花了一分钟,那仍然太长了。
Dunk 2012年

2
在版本控制下在模块X中工作的任何人每次希望工作时都必须获得模块X和枚举。此外,如果每次更改枚举文件时,所做的更改都可能影响项目中的每个模块,那么它将作为一种耦合形式给我留下深刻印象。如果您的项目足够大,以致所有或大多数团队成员都不了解项目的每个部分,那么全局枚举是非常危险的。全局不可变变量甚至没有全局可变变量差,但它仍然不是理想的。也就是说,对于拥有少于10名成员的团队来说,全局枚举通常是可以的。
Brian

3

对我而言,这一切都取决于您的项目范围。如果只有一个带有10个结构的文件,而这是有史以来唯一使用的文件,那么我非常愿意拥有一个.h文件。如果您有几种不同类型的功能,例如单元,经济,建筑物等,那么我肯定会把它们分开。创建一个units.h,其中所有处理单位的结构都驻留在其中。如果您想对某处的单位进行某些操作,则需要包含unit.h,但这是一个很好的“标识符”,它可以在此文件中完成对单位的操作。

这样看,您不需要买超市,因为您需要一罐可乐;)


3

我不是C ++开发人员,因此此答案将对OOA&D更为通用:

通常,应将代码对象按照功能相关性进行分组,而不必按照特定于语言的构造进行分组。始终应该问的关键是或否问题是:“在使用库时,最终编码器是否应使用它们获得的大部分或全部对象?” 如果是,请分组。如果不是,请考虑将代码对象拆分并将其放置在需要它们的其他对象附近(从而增加使用方需要他们实际访问的所有内容的机会)。

基本概念是“高凝聚力”。代码成员(从类的方法一直到名称空间或DLL中的类以及DLL本身)应进行组织,以使编码器可以包含所需的所有内容,而无需包含任何内容。这使整体设计更能容忍变化。可以更改必须更改的东西,而不会影响其他不必更改的代码对象。在许多情况下,它还使应用程序的存储效率更高。DLL完全加载到内存中,而不管进程中执行了多少指令。因此,设计“精简”应用程序需要注意将多少代码提取到内存中。

这个概念几乎适用于所有级别的代码组织,对可维护性,内存效率,性能,构建速度等都有不同程度的影响。如果编码人员仅为了访问一个对象而需要引用相当大的整体头文件/ DLL,它不依赖于该DLL中的任何其他对象,则应该重新考虑该对象在DLL中的包含。但是也有可能走得太远。每个类的DLL都是一个坏主意,因为它会降低构建速度(需要关联的开销来重建更多的DLL)并使版本控制成为噩梦。

举例说明:如果对代码库的任何实际使用都涉及使用要放入此单个“ enumerations.h”文件中的大多数或全部枚举,则一定要将它们分组在一起;您将知道在哪里可以找到它们。但是,如果可以想象的是,您的使用编码器可能只需要您在标头中提供的十二个枚举中的一两个,那么我建议您将它们放入一个单独的库中,并使其与其余枚举中较大的一个依赖。这样一来,您的编码人员就可以只获得他们想要的一个或两个,而无需链接到更单一的DLL。


2

当您有多个开发人员在同一个代码库上工作时,这种事情就成为问题。

我当然已经看到类似的全局文件成为合并冲突和(较大)项目上各种麻烦的纽带。

但是,如果您是唯一从事该项目的人,那么您应该做自己最喜欢的事情,并且只有在了解(并同意)其背后的动机后才采用“最佳实践”。

最好要犯一些错误并向他们学习,而不是冒着一生都沉迷于货物崇拜编程实践的风险。


1

是的,在一个大项目中这样做是不好的做法。吻。

年轻的同事在核心.h文件中重命名了一个简单变量,而100名工程师等了45分钟才能重建所有文件,这影响了每个人的性能。;)

多年来,所有项目都是从小规模,热气球开始的,我们诅咒那些尽早捷径制造技术债务的人。最佳做法,将全局.h内容限制为必须是全局的。


如果有人(年轻人或老人,同一个)可以(让他们开心)让每个人都重建项目,您就不使用审核系统吗?
Sanctus '18年

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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.