Đây có phải là một thực tiễn tồi khi bao gồm tất cả các enum trong một tệp và sử dụng nó trong nhiều lớp không?


12

Tôi là một nhà phát triển trò chơi đầy tham vọng, tôi thỉnh thoảng làm việc trong các trò chơi độc lập và trong một thời gian tôi đã làm một việc có vẻ như là một thực tiễn tồi tệ, nhưng tôi thực sự muốn nhận được câu trả lời từ một số lập trình viên có kinh nghiệm ở đây.

Giả sử tôi có một tệp được gọi là enumList.hnơi tôi khai báo tất cả các enum tôi muốn sử dụng trong trò chơi của mình:

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

Ý tưởng chính là tôi khai báo tất cả các enum trong trò chơi trong 1 tệp , sau đó nhập tệp đó khi tôi cần sử dụng một enum nào đó từ nó, thay vì khai báo nó trong tệp mà tôi cần sử dụng. Tôi làm điều này bởi vì nó làm cho mọi thứ sạch sẽ, tôi có thể truy cập mọi enum ở 1 nơi thay vì mở các trang chỉ để truy cập một enum.

Đây có phải là một thực hành xấu và nó có thể ảnh hưởng đến hiệu suất trong bất kỳ cách nào?


1
Cấu trúc nguồn không thể ảnh hưởng đến hiệu suất - nó vẫn sẽ được biên dịch thành như nhau, bất kể các enum ở đâu. Vì vậy, thực sự đây là một câu hỏi về nơi tốt nhất để đặt chúng, và đối với các enum nhỏ, một tệp không có vẻ quá cấm kỵ.
Donffan Donal

Tôi đã hỏi trong những trường hợp cực đoan hơn, một trò chơi có thể có nhiều enum và chúng có thể khá lớn, nhưng cảm ơn vì nhận xét của bạn
Bugster

4
Nó sẽ không ảnh hưởng đến hiệu suất ứng dụng, nhưng nó sẽ có tác động tiêu cực đến thời gian biên dịch. Ví dụ: nếu bạn thêm một tài liệu vào materials_tcác tệp không xử lý tài liệu sẽ phải được xây dựng lại.
Gort Robot

14
Nó giống như đặt tất cả các ghế trong nhà của bạn trong phòng ghế, vì vậy nếu bạn muốn ngồi xuống, bạn biết nơi để đi.
kevin cline

Bên cạnh đó, bạn có thể dễ dàng thực hiện cả hai , bằng cách đặt từng enum vào tệp riêng của mình và enumList.hphục vụ như một bộ sưu tập #includes cho các tệp đó. Điều này cho phép các tệp chỉ cần một enum để lấy nó trực tiếp, trong khi cung cấp một gói số ít cho bất cứ thứ gì thực sự muốn tất cả chúng.
Thời gian của Justin - Phục hồi lại

Câu trả lời:


35

Tôi thực sự nghĩ rằng đó là một thực hành xấu. Khi chúng tôi đo lường chất lượng mã, có một cái gì đó chúng tôi gọi là "độ chi tiết". Độ chi tiết của bạn bị ảnh hưởng nghiêm trọng bằng cách đặt tất cả các enum đó vào một tệp duy nhất và do đó khả năng duy trì cũng bị ảnh hưởng.

Tôi có một tệp duy nhất cho mỗi enum để tìm thấy nó một cách nhanh chóng và nhóm nó với mã hành vi của chức năng cụ thể (ví dụ: enum vật liệu trong thư mục có hành vi vật chất là v.v.);

Ý tưởng chính là tôi khai báo tất cả các enum trong trò chơi trong 1 tệp, sau đó nhập tệp đó khi tôi cần sử dụng một enum nào đó từ nó, thay vì khai báo nó trong tệp mà tôi cần sử dụng. Tôi làm điều này bởi vì nó làm cho mọi thứ sạch sẽ, tôi có thể truy cập mọi enum ở 1 nơi thay vì mở các trang chỉ để truy cập một enum.

Bạn có thể nghĩ rằng nó sạch sẽ, nhưng trên thực tế, nó không phải là. Nó kết hợp những thứ không thuộc về chức năng và mô-đun thông minh và làm giảm tính mô-đun của ứng dụng của bạn. Tùy thuộc vào kích thước của cơ sở mã của bạn và mô-đun bạn muốn mã của bạn được cấu trúc như thế nào, điều này có thể phát triển thành một vấn đề lớn hơn và mã / phụ thuộc ô uế trong các phần khác của hệ thống. Tuy nhiên, nếu bạn chỉ viết một hệ thống nhỏ, nguyên khối, điều này không nhất thiết phải áp dụng. Tuy nhiên, tôi sẽ không làm điều đó như thế này ngay cả đối với một hệ thống nguyên khối nhỏ.


2
+1 để đề cập đến độ chi tiết, tôi đã xem xét các câu trả lời khác nhưng bạn đã đưa ra một quan điểm tốt.
Bugster

+1 cho một tệp cho mỗi enum. cho một cái dễ tìm hơn
mauris

11
Chưa kể rằng việc thêm một giá trị enum được sử dụng ở một nơi sẽ khiến mọi tệp trong toàn bộ dự án của bạn phải được xây dựng lại.
Gort Robot

lời khuyên tốt. dù sao bạn cũng đã thay đổi suy nghĩ của mình .. Tôi luôn thích đi CompanyNamespace.Enums .... và nhận được một danh sách dễ dàng, nhưng nếu cấu trúc mã bị kỷ luật, thì cách tiếp cận của bạn sẽ tốt hơn
Matt Evans

21

Vâng, đó là thực tế xấu, không phải vì hiệu suất mà vì khả năng bảo trì.

Nó làm cho mọi thứ "sạch" chỉ trong OCD "thu thập những thứ tương tự lại với nhau". Nhưng đó thực sự không phải là loại "sạch" hữu ích và tốt.

Các thực thể mã phải được nhóm lại để tối đa hóa sự gắn kết và giảm thiểu khớp nối , điều này đạt được tốt nhất bằng cách nhóm chúng thành các mô đun chức năng độc lập chủ yếu. Nhóm chúng theo các tiêu chí kỹ thuật (chẳng hạn như đặt tất cả các enum lại) đạt được điều ngược lại - đó là mã cặp đôi hoàn toàn không có tính tương đối chức năng và đặt các enum chỉ có thể được sử dụng ở một nơi vào một tệp khác.


3
Thật; 'chỉ trong OCD "thu thập những thứ tương tự lại với nhau". 100 upvote nếu tôi có thể.
Dogweather

Tôi sẽ giúp chỉnh sửa chính tả nếu có thể, nhưng bạn đã không mắc đủ lỗi để vượt qua ngưỡng 'chỉnh sửa' của SE. :-P
Dogweather

thú vị là hầu hết tất cả các khung web đều yêu cầu các tệp được thu thập theo loại thay vì theo tính năng.
kevin cline

@kevincline: Tôi sẽ không nói "gần như tất cả" - chỉ những người dựa trên cấu hình quá mức và thông thường, những người này cũng có một khái niệm mô-đun cho phép nhóm mã theo chức năng.
Michael Borgwardt

8

Vâng, đây chỉ là dữ liệu (không phải hành vi). Điều tồi tệ nhất có thể xảy ra / về mặt lý thuyết là cùng một mã được bao gồm và biên dịch nhiều lần tạo ra một chương trình tương đối lớn hơn.

Rõ ràng là không thể bao gồm những điều đó có thể thêm nhiều chu kỳ vào hoạt động của bạn, với điều kiện là không có bất kỳ mã hành vi / thủ tục nào bên trong chúng (không có vòng lặp, không có if, v.v.).

Một chương trình (tương đối) lớn hơn khó có thể ảnh hưởng đến hiệu suất (tốc độ thực hiện) và, trong mọi trường hợp, chỉ là mối quan tâm lý thuyết từ xa. Hầu hết (có thể là tất cả) trình biên dịch quản lý bao gồm theo cách ngăn chặn các vấn đề đó.

IMHO, những lợi thế mà bạn có được với một tệp duy nhất (mã dễ đọc hơn và dễ quản lý hơn) phần lớn vượt quá mọi nhược điểm có thể có.


5
Bạn đưa ra một quan điểm rất hay mà tôi nghĩ rằng tất cả các câu trả lời phổ biến hơn đang bỏ qua - dữ liệu bất biến không có bất kỳ khớp nối nào cả.
Michael Shaw

3
Tôi đoán bạn chưa bao giờ phải làm việc với các dự án sẽ mất nửa giờ hoặc lâu hơn để biên dịch. Nếu bạn có tất cả các enum của mình trong một tệp và thay đổi một enum, đó là thời gian nghỉ dài. Heck, ngay cả khi chỉ mất một phút, vẫn còn quá lâu.
Dunk

2
Bất cứ ai làm việc trong mô-đun X dưới sự kiểm soát phiên bản đều phải nhận được mô-đun X và enum mỗi khi họ muốn làm việc. Hơn nữa, nó tấn công tôi như một hình thức ghép nếu, mỗi khi bạn thay đổi tệp enum, thay đổi của bạn có khả năng ảnh hưởng đến mọi mô-đun duy nhất trong dự án của bạn. Nếu dự án của bạn đủ lớn để tất cả hoặc hầu hết các thành viên trong nhóm không hiểu được từng phần của dự án, thì một enum toàn cầu là khá nguy hiểm. Một biến số bất biến toàn cầu thậm chí không gần như là một biến đổi toàn cầu, nhưng nó vẫn không lý tưởng. Điều đó nói rằng, một enum toàn cầu có lẽ hầu hết là ok với một đội có <10 thành viên.
Brian

3

Đối với tôi tất cả phụ thuộc vào phạm vi dự án của bạn. Nếu có một tệp có 10 cấu trúc và đó là những tệp duy nhất từng được sử dụng, tôi hoàn toàn thoải mái khi có một tệp .h cho nó. Nếu bạn có một số loại chức năng khác nhau, giả sử một đơn vị, nền kinh tế, tòa nhà, v.v ... tôi chắc chắn sẽ chia nhỏ mọi thứ. Tạo một đơn vị.h nơi tất cả các cấu trúc liên quan đến các đơn vị cư trú. Nếu bạn muốn làm một cái gì đó với các đơn vị ở đâu đó, bạn cần phải bao gồm các đơn vị. Chắc chắn, nhưng đó cũng là một "định danh" đẹp mà một cái gì đó với các đơn vị có thể được thực hiện trong tệp này.

Nhìn nó theo cách này, bạn không mua siêu thị vì bạn cần một lon coca;)


3

Tôi không phải là nhà phát triển C ++, vì vậy câu trả lời này sẽ chung chung hơn với OOA & D:

Thông thường, các đối tượng mã phải được nhóm theo mức độ phù hợp về chức năng, không nhất thiết phải theo các cấu trúc ngôn ngữ cụ thể. Chìa khóa có - không có câu hỏi nào luôn được hỏi là "bộ mã hóa cuối sẽ được sử dụng hầu hết hoặc tất cả các đối tượng mà họ nhận được khi sử dụng thư viện?" Nếu có, nhóm đi. Nếu không, hãy xem xét tách các đối tượng mã lên và đặt chúng gần hơn với các đối tượng khác cần chúng (do đó làm tăng cơ hội người tiêu dùng sẽ cần mọi thứ họ thực sự đang truy cập).

Khái niệm cơ bản là "sự gắn kết cao"; các thành viên mã (từ các phương thức của một lớp cho đến các lớp trong một không gian tên hoặc DLL và chính DLL) nên được tổ chức sao cho một lập trình viên có thể bao gồm mọi thứ họ cần, và không có gì họ không làm. Điều này làm cho thiết kế tổng thể trở nên khoan dung hơn với sự thay đổi; những thứ phải thay đổi có thể, mà không ảnh hưởng đến các đối tượng mã khác không phải thay đổi. Trong nhiều trường hợp, nó cũng làm cho ứng dụng hiệu quả hơn về bộ nhớ; một DLL được tải toàn bộ vào bộ nhớ, bất kể bao nhiêu trong số các lệnh đó đã được thực thi bởi quy trình. Do đó, thiết kế các ứng dụng "nạc" đòi hỏi phải chú ý đến lượng mã được kéo vào bộ nhớ.

Khái niệm này áp dụng ở hầu hết tất cả các cấp tổ chức mã, với các mức độ ảnh hưởng khác nhau đến khả năng bảo trì, hiệu quả bộ nhớ, hiệu suất, tốc độ xây dựng, v.v. Nếu một lập trình viên cần tham chiếu một tiêu đề / DLL có kích thước khá nguyên khối chỉ để truy cập vào một đối tượng, không phụ thuộc vào bất kỳ đối tượng nào khác trong DLL đó, thì việc đưa đối tượng đó vào DLL có lẽ nên được xem xét lại. Tuy nhiên, có thể đi quá xa theo cách khác; một DLL cho mỗi lớp là một ý tưởng tồi, bởi vì nó làm chậm tốc độ xây dựng (nhiều DLL hơn để xây dựng lại với chi phí liên kết) và khiến phiên bản trở thành một cơn ác mộng.

Trường hợp cụ thể: nếu bất kỳ việc sử dụng thư viện mã trong thế giới thực của bạn sẽ liên quan đến việc sử dụng hầu hết hoặc tất cả các bảng liệt kê mà bạn đang đưa vào tệp "enumerations.h" này, thì bằng mọi cách có thể nhóm chúng lại với nhau; bạn sẽ biết nơi để tìm chúng. Nhưng nếu lập trình viên tiêu thụ của bạn có thể hình dung chỉ cần một hoặc hai trong số hàng chục bảng liệt kê bạn đang cung cấp trong tiêu đề, thì tôi sẽ khuyến khích bạn đặt chúng vào một thư viện riêng và biến nó thành một phần phụ thuộc của phần lớn hơn với phần còn lại của bảng liệt kê . Điều đó cho phép các lập trình viên của bạn chỉ nhận được một hoặc hai cái họ muốn mà không cần liên kết với DLL nguyên khối hơn.


2

Điều này trở thành một vấn đề khi bạn có nhiều nhà phát triển làm việc trên cùng một cơ sở mã.

Tôi chắc chắn đã thấy các tệp toàn cầu tương tự trở thành một mối quan hệ cho các vụ va chạm hợp nhất và tất cả các loại đau buồn đối với các dự án (lớn hơn).

Tuy nhiên, nếu bạn là người duy nhất làm việc trong dự án, thì bạn nên làm bất cứ điều gì bạn thấy thoải mái nhất và chỉ áp dụng "thực tiễn tốt nhất" nếu bạn hiểu (và đồng ý) động lực đằng sau chúng.

Tốt hơn là phạm một số sai lầm và học hỏi từ chúng hơn là có nguy cơ bị mắc kẹt với các thực hành lập trình sùng bái hàng hóa cho đến hết đời.


1

Vâng, đó là thực tế xấu để làm như vậy trên một dự án lớn. HÔN.

Đồng nghiệp trẻ đã đổi tên một biến đơn giản trong tệp .h lõi và 100 kỹ sư đã đợi 45 phút để tất cả các tệp của họ được xây dựng lại, điều này ảnh hưởng đến hiệu suất của mọi người. ;)

Tất cả các dự án bắt đầu nhỏ, khinh khí cầu trong nhiều năm và chúng tôi nguyền rủa những người đã sử dụng các phím tắt sớm để tạo ra nợ kỹ thuật. Thực tiễn tốt nhất, giữ cho nội dung .h toàn cầu giới hạn ở những gì nhất thiết phải là toàn cầu.


Bạn không sử dụng hệ thống đánh giá nếu ai đó (trẻ hay già, giống nhau) chỉ có thể (không vui) khiến mọi người xây dựng lại dự án, phải không?
Sanctus

1

Trong trường hợp này, tôi muốn nói câu trả lời lý tưởng là nó phụ thuộc vào cách thức các enum được tiêu thụ, nhưng trong hầu hết các trường hợp, có lẽ tốt nhất là xác định tất cả các enum riêng biệt, nhưng nếu bất kỳ trong số chúng đã được ghép bởi thiết kế, bạn nên cung cấp một phương tiện giới thiệu nói chung ghép enums. Trong thực tế, bạn có một dung sai khớp nối lên đến số lượng khớp nối có chủ ý đã có, nhưng không còn nữa.

Xem xét điều này, giải pháp linh hoạt nhất có khả năng xác định từng enum trong một tệp riêng biệt, nhưng cung cấp các gói được ghép khi hợp lý để làm như vậy (như được xác định bởi mục đích sử dụng của enum liên quan).


Xác định tất cả các liệt kê của bạn trong cùng một tệp kết hợp chúng với nhau và bởi phần mở rộng gây ra bất kỳ mã nào phụ thuộc vào một hoặc nhiều enum phụ thuộc vào tất cả các enum, bất kể mã đó có thực sự sử dụng bất kỳ enum nào khác không.

#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()thay vì chỉ biết về nó map_t, bởi vì nếu không thì mọi thay đổi đối với người khác sẽ ảnh hưởng đến nó mặc dù nó không thực sự tương tác với những người khác.

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

void renderMap(map_t, mapIndex);

Tuy nhiên, trong trường hợp các thành phần đã được ghép nối với nhau, việc cung cấp nhiều enum trong một gói có thể dễ dàng cung cấp thêm sự rõ ràng và đơn giản, miễn là có một lý do hợp lý rõ ràng cho các enum được ghép nối, việc sử dụng các enum đó cũng được ghép nối, và việc cung cấp cho họ cũng không giới thiệu bất kỳ khớp nối bổ sung nào.

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

Trong trường hợp này, không có kết nối trực tiếp, hợp lý giữa loại thực thể và loại vật liệu (giả sử rằng các thực thể không được làm từ một trong các vật liệu được xác định). Tuy nhiên, nếu chúng ta gặp trường hợp, ví dụ, một enum rõ ràng phụ thuộc vào nhau, thì việc cung cấp một gói duy nhất chứa tất cả các enum ghép (cũng như bất kỳ thành phần ghép nào khác), để khớp nối có thể cô lập với gói đó càng nhiều càng tố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()];
}

Nhưng than ôi ... ngay cả khi hai enum thực chất được ghép với nhau, ngay cả khi nó là thứ gì đó mạnh như "enum thứ hai cung cấp các thể loại con cho enum đầu tiên", vẫn có thể đến lúc chỉ cần một trong số các enum.

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

Do đó, vì chúng ta biết cả hai luôn có thể có các tình huống xác định nhiều cách liệt kê trong một tệp có thể thêm khớp nối không cần thiết và việc cung cấp các enum ghép trong một gói có thể làm rõ cách sử dụng dự định và cho phép chúng ta tự cô lập mã khớp nối thực tế như càng nhiều càng tốt, giải pháp lý tưởng là xác định riêng từng kiểu liệt kê và cung cấp các gói chung cho bất kỳ enum nào được dự định thường xuyên được sử dụng cùng nhau. Các enum duy nhất được xác định trong cùng một tệp sẽ là những cái được liên kết nội tại với nhau, như vậy việc sử dụng cái này cũng bắt buộc phải sử dụng cái kia.

// 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"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.