Apakah praktik yang buruk untuk memasukkan semua enum dalam satu file dan menggunakannya dalam beberapa kelas?

12

Saya seorang pengembang game yang bercita-cita tinggi, saya bekerja pada game indie sesekali, dan untuk sementara waktu saya telah melakukan sesuatu yang tampak seperti praktik yang buruk pada awalnya, tetapi saya benar-benar ingin mendapatkan jawaban dari beberapa programmer yang berpengalaman di sini.

Katakanlah saya memiliki file bernama enumList.htempat saya mendeklarasikan semua enum yang ingin saya gunakan dalam game saya:

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

Gagasan utamanya adalah bahwa saya mendeklarasikan semua enum dalam game dalam 1 file , dan kemudian mengimpor file itu ketika saya perlu menggunakan enum tertentu darinya, daripada mendeklarasikannya dalam file di mana saya perlu menggunakannya. Saya melakukan ini karena itu membuat hal-hal bersih, saya dapat mengakses setiap enum di 1 tempat daripada membuka halaman hanya untuk mengakses satu enum.

Apakah ini praktik yang buruk dan dapatkah hal itu memengaruhi kinerja?

Bugster
sumber
1
Struktur sumber tidak dapat memengaruhi kinerja - masih akan dikompilasi ke bawah, di mana pun enum berada. Jadi sebenarnya ini adalah pertanyaan tentang di mana tempat terbaik untuk meletakkannya, dan untuk enum kecil, satu file tidak terdengar terlalu tabu.
Steffan Donal
Saya bertanya dalam kasus yang lebih ekstrem, sebuah game dapat memiliki banyak enum dan bisa sangat besar, tetapi terima kasih atas komentar Anda
Bugster
4
Itu tidak akan mempengaruhi kinerja aplikasi, tetapi itu akan memiliki efek negatif pada waktu kompilasi. Misalnya, jika Anda menambahkan materi ke materials_tfile yang tidak berurusan dengan materi harus dibangun kembali.
Gort the Robot
14
itu seperti meletakkan semua kursi di rumah Anda di ruang kursi, jadi jika Anda ingin duduk, Anda tahu ke mana harus pergi.
kevin cline
Sebagai tambahan, Anda dapat dengan mudah melakukan keduanya , dengan meletakkan masing-masing enum dalam file-nya sendiri dan telah enumList.hmelayani sebagai koleksi #includes untuk file-file tersebut. Ini memungkinkan file yang hanya memerlukan satu enum untuk mendapatkannya secara langsung, sambil menyediakan paket tunggal untuk apa pun yang benar-benar menginginkan semuanya.
Justin Time - Pasang kembali Monica

Jawaban:

35

Saya benar-benar berpikir itu adalah praktik yang buruk. Ketika kita mengukur kualitas kode, ada sesuatu yang kita sebut "granularity". Granularity Anda sangat menderita dengan meletakkan semua enum tersebut dalam satu file tunggal dan karenanya rawatan juga akan terganggu.

Saya akan memiliki satu file per enum untuk menemukannya dengan cepat dan mengelompokkannya dengan kode perilaku fungsi spesifik (misalnya bahan enum dalam folder di mana perilaku material dll.);

Gagasan utamanya adalah bahwa saya mendeklarasikan semua enum dalam game dalam 1 file, dan kemudian mengimpor file itu ketika saya perlu menggunakan enum tertentu darinya, daripada mendeklarasikannya dalam file di mana saya perlu menggunakannya. Saya melakukan ini karena itu membuat hal-hal bersih, saya dapat mengakses setiap enum di 1 tempat daripada membuka halaman hanya untuk mengakses satu enum.

Anda mungkin berpikir itu bersih, tetapi kenyataannya tidak. Ini menggabungkan hal-hal yang tidak termasuk bersama fungsionalitas dan modul dan mengurangi modularitas aplikasi Anda. Tergantung pada ukuran basis kode Anda dan bagaimana modular Anda ingin kode Anda terstruktur, ini dapat berkembang menjadi masalah yang lebih besar dan kode / dependensi yang tidak bersih di bagian lain dari sistem Anda. Namun, jika Anda hanya menulis sistem monolitik yang kecil, ini tidak berlaku. Namun, saya tidak akan melakukannya seperti ini bahkan untuk sistem monolitik kecil.

Elang
sumber
2
+1 untuk menyebutkan rincian, saya mengambil jawaban lain sebagai pertimbangan tetapi Anda membuat poin yang bagus.
Bugster
+1 untuk satu file per enum. untuk satu lebih mudah ditemukan.
mauris
11
Belum lagi menambahkan nilai enum tunggal yang digunakan di satu tempat akan menyebabkan setiap file di seluruh proyek Anda harus dibangun kembali.
Gort the Robot
saran yang bagus. Anda mengubah pikiran saya .. Saya selalu suka pergi CompanyNamespace.Enums .... dan mendapatkan daftar yang mudah, tetapi jika struktur kode disiplin, maka pendekatan Anda lebih baik
Matt Evans
21

Ya, ini praktik yang buruk, bukan karena kinerja tetapi karena pemeliharaan.

Itu membuat hal-hal "bersih" hanya di OCD "mengumpulkan hal-hal serupa" cara. Tapi itu sebenarnya bukan jenis "kebersihan" yang bermanfaat dan bagus.

Entitas kode harus dikelompokkan untuk memaksimalkan kohesi dan meminimalkan pemasangan , yang paling baik dicapai dengan mengelompokkannya ke dalam modul fungsional yang sebagian besar independen. Pengelompokan mereka berdasarkan kriteria teknis (seperti menyatukan semua enum) mencapai yang sebaliknya - ini memasangkan kode yang sama sekali tidak memiliki hubungan fungsional dan menempatkan enum yang mungkin hanya digunakan di satu tempat ke dalam file yang berbeda.

Michael Borgwardt
sumber
3
Benar; 'hanya di OCD "kumpulkan hal-hal serupa" jalan ". 100 upvotes jika saya bisa.
Dogweather
Saya akan membantu mengedit ejaan jika saya bisa, tetapi Anda tidak membuat cukup kesalahan untuk melewati ambang SE 'edit'. :-P
Dogweather
menarik bahwa hampir semua kerangka kerja web membutuhkan file untuk dikumpulkan berdasarkan jenis, bukan oleh fitur.
kevin cline
@ kevincline: Saya tidak akan mengatakan "hampir semua" - hanya yang didasarkan pada konfigurasi-over-konfigurasi, dan biasanya, mereka juga memiliki konsep modul yang memungkinkan pengelompokan kode secara fungsional.
Michael Borgwardt
8

Nah, ini hanya data (bukan perilaku). Yang terburuk yang mungkin / secara teoritis terjadi adalah bahwa kode yang sama dimasukkan dan dikompilasi lebih dari sekali menghasilkan program yang relatif lebih besar.

Sangatlah mustahil untuk memasukkan hal-hal tersebut ke dalam siklus operasi Anda, mengingat tidak ada kode perilaku / prosedural di dalamnya (tidak ada loop, tidak ada jika, dll).

Suatu program (yang relatif) lebih besar hampir tidak dapat memengaruhi kinerja (kecepatan eksekusi) dan, bagaimanapun juga, hanyalah masalah teoretis jarak jauh. Sebagian besar (mungkin semua) kompiler mengelola termasuk dengan cara yang mencegah masalah seperti itu.

IMHO, kelebihan yang Anda dapatkan dengan satu file (kode lebih mudah dibaca dan lebih mudah dikelola) sebagian besar melebihi kelemahan yang mungkin ada.

AlexBottoni
sumber
5
Anda membuat poin yang sangat bagus yang saya pikir semua jawaban yang lebih populer diabaikan - data tidak berubah sama sekali tidak memiliki kopling.
Michael Shaw
3
Saya kira Anda tidak pernah harus mengerjakan proyek yang membutuhkan waktu setengah jam atau lebih untuk dikompilasi. Jika Anda memiliki semua enum dalam satu file dan mengubah satu enum, saatnya untuk istirahat lama. Heck, bahkan jika hanya butuh satu menit, itu masih terlalu lama.
Dunk
2
Siapa pun yang bekerja di modul X di bawah kontrol versi harus mendapatkan modul X dan enum setiap kali mereka ingin bekerja. Lebih jauh, itu menurut saya sebagai bentuk penggandengan jika, setiap kali Anda mengubah file enum, perubahan Anda berpotensi mempengaruhi setiap modul tunggal dalam proyek Anda. Jika proyek Anda cukup besar sehingga semua atau sebagian besar anggota tim tidak memahami setiap bagian dari proyek, enum global cukup berbahaya. Variabel global yang tidak berubah bahkan tidak seburuk variabel global yang bisa berubah, tetapi masih belum ideal. Yang mengatakan, enum global mungkin sebagian besar ok di tim dengan <10 anggota.
Brian
3

Bagi saya itu semua tergantung pada ruang lingkup proyek Anda. Jika ada satu file dengan katakanlah 10 struct dan itu adalah satu-satunya yang pernah digunakan, saya akan sangat nyaman memiliki satu file .h untuk itu. Jika Anda memiliki beberapa jenis fungsi, katakanlah unit, ekonomi, bangunan, dll. Saya pasti akan membagi beberapa hal. Buat units.h di mana semua struct yang berhubungan dengan unit berada. Jika Anda ingin melakukan sesuatu dengan unit di suatu tempat Anda harus menyertakan units.h yakin, tetapi juga merupakan "pengidentifikasi" yang bagus bahwa sesuatu dengan unit mungkin dilakukan dalam file ini.

Lihatlah dengan cara ini, Anda tidak membeli supermarket karena Anda memerlukan sekaleng coke;)

hoppa
sumber
3

Saya bukan pengembang C ++, jadi jawaban ini akan lebih umum untuk OOA & D:

Biasanya, objek kode harus dikelompokkan dalam hal relevansi fungsional, tidak harus dalam hal konstruksi spesifik bahasa. Pertanyaan utama ya-tidak yang harus selalu ditanyakan adalah, "akankah pembuat kode akhir diharapkan menggunakan sebagian besar atau semua objek yang mereka dapatkan saat mengonsumsi perpustakaan?" Jika ya, kelompokkan. Jika tidak, pertimbangkan untuk memecah objek kode dan menempatkannya lebih dekat ke objek lain yang membutuhkannya (sehingga meningkatkan kemungkinan bahwa konsumen akan membutuhkan semua yang mereka akses).

Konsep dasarnya adalah "kohesi tinggi"; anggota kode (dari metode kelas hingga kelas di namespace atau DLL, dan DLL sendiri) harus diatur sedemikian rupa sehingga pembuat kode dapat memasukkan semua yang mereka butuhkan, dan tidak ada yang tidak mereka lakukan. Ini membuat desain keseluruhan lebih toleran terhadap perubahan; hal-hal yang harus diubah bisa, tanpa memengaruhi objek kode lain yang tidak perlu diubah. Dalam banyak keadaan, ini juga membuat aplikasi lebih hemat memori; DLL dimuat seluruhnya ke dalam memori, terlepas dari berapa banyak instruksi yang pernah dieksekusi oleh proses. Oleh karena itu, merancang aplikasi "lean" perlu memperhatikan seberapa banyak kode yang ditarik ke dalam memori.

Konsep ini berlaku di hampir semua level organisasi kode, dengan berbagai tingkat dampak pada pemeliharaan, efisiensi memori, kinerja, kecepatan bangun, dll. Jika seorang koder perlu merujuk header / DLL yang ukurannya agak monolitik hanya untuk mengakses satu objek, yang tidak bergantung pada objek lain di DLL itu, maka penyertaan objek itu di DLL mungkin harus dipikirkan kembali. Namun mungkin juga untuk melangkah terlalu jauh; DLL untuk setiap kelas adalah ide yang buruk, karena memperlambat kecepatan pembuatan (lebih banyak DLL untuk dibangun kembali dengan overhead terkait) dan menjadikan versi sebagai mimpi buruk.

Contoh kasus: jika penggunaan perpustakaan kode Anda di dunia nyata melibatkan sebagian besar atau semua enumerasi yang Anda masukkan ke dalam file "enumerations.h" tunggal ini, maka tentu saja kelompokkan mereka bersama-sama; Anda akan tahu di mana mencarinya untuk menemukan mereka. Tetapi jika pengkode pengkonsumsi Anda mungkin hanya membutuhkan satu atau dua dari selusin enumerasi yang Anda berikan di header, daripada saya akan mendorong Anda untuk menempatkan mereka ke perpustakaan yang terpisah dan menjadikannya ketergantungan dari yang lebih besar dengan sisa enumerasi . Itu memungkinkan coders Anda mendapatkan hanya satu atau dua yang mereka inginkan tanpa menautkan ke DLL yang lebih monolitik.

KeithS
sumber
2

Hal semacam ini menjadi masalah ketika Anda memiliki beberapa pengembang bekerja pada basis kode yang sama.

Saya tentu saja melihat file global serupa menjadi nexus untuk menggabungkan tabrakan dan segala macam kesedihan pada proyek (lebih besar).

Namun, jika Anda adalah satu-satunya yang mengerjakan proyek, maka Anda harus melakukan apa pun yang paling Anda sukai, dan hanya mengadopsi "praktik terbaik" jika Anda memahami (dan setuju dengan) motivasi di baliknya.

Lebih baik membuat beberapa kesalahan dan belajar darinya daripada mengambil risiko terjebak dengan praktik pemrograman kultus kargo selama sisa hidup Anda.

William Payne
sumber
1

Ya, itu adalah praktik buruk untuk melakukannya pada proyek besar. CIUMAN.

Rekan kerja muda mengganti nama variabel sederhana dalam file inti .h dan 100 insinyur menunggu 45 menit untuk semua file mereka dibangun kembali, yang memengaruhi kinerja semua orang. ;)

Semua proyek dimulai dari kecil, balon selama bertahun-tahun, dan kami mengutuk mereka yang mengambil jalan pintas awal untuk menciptakan utang teknis. Praktik terbaik, jaga agar konten global .h terbatas pada apa yang seharusnya bersifat global.

AmyInNH
sumber
Anda tidak menggunakan sistem ulasan jika seseorang (tua atau muda, yang sama) dapat (tidak senang) membuat semua orang membangun kembali proyek, bukan?
Sanctus
1

Dalam hal ini, saya akan mengatakan jawaban yang ideal adalah bahwa itu tergantung pada bagaimana enum dikonsumsi, tetapi dalam sebagian besar keadaan mungkin yang terbaik untuk mendefinisikan semua enum secara terpisah, tetapi jika salah satu dari mereka sudah digabungkan dengan desain, Anda harus memberikan sarana untuk memperkenalkan enum yang digabungkan tersebut secara kolektif. Akibatnya, Anda memiliki toleransi kopling hingga jumlah kopling yang disengaja sudah ada, tetapi tidak lebih.

Mempertimbangkan hal ini, solusi yang paling fleksibel kemungkinan untuk mendefinisikan masing-masing enum dalam file yang terpisah, tetapi menyediakan paket yang digabungkan ketika masuk akal untuk melakukannya (sebagaimana ditentukan oleh penggunaan yang dimaksud dari enum yang terlibat).


Menentukan semua enumerasi Anda di file yang sama memasangkannya bersama-sama, dan dengan ekstensi menyebabkan kode apa pun yang bergantung pada satu atau lebih enum bergantung pada semua enum, terlepas dari apakah kode tersebut benar-benar menggunakan enum lain.

#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()lebih suka hanya tahu tentang map_t, karena jika tidak ada perubahan pada yang lain akan mempengaruhinya meskipun tidak benar-benar berinteraksi dengan yang lain.

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

void renderMap(map_t, mapIndex);

Namun, dalam kasus di mana komponen sudah digabungkan bersama, menyediakan beberapa enum dalam satu paket dapat dengan mudah memberikan kejelasan dan kesederhanaan tambahan, asalkan ada alasan logis yang jelas untuk enum yang akan digabungkan, bahwa penggunaan enum tersebut juga digabungkan, dan bahwa menyediakannya juga tidak memperkenalkan sambungan tambahan.

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

Dalam hal ini, tidak ada koneksi logis langsung antara tipe entitas dan tipe material (dengan asumsi bahwa entitas tidak terbuat dari salah satu material yang ditentukan). Namun, jika kami memiliki kasus di mana, misalnya, satu enum secara eksplisit tergantung pada yang lain, maka masuk akal untuk menyediakan satu paket berisi semua enum yang digabungkan (serta komponen yang digabungkan lainnya), sehingga penggandengan dapat terisolasi ke paket itu sebanyak mungkin.

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

Tetapi sayang ... bahkan ketika dua enum secara intrinsik digabungkan bersama, bahkan jika itu sekuat "enum kedua menyediakan subkategori untuk enum pertama", mungkin masih ada waktu di mana hanya satu enum yang diperlukan.

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

Oleh karena itu, karena kita tahu keduanya bahwa selalu ada situasi di mana mendefinisikan beberapa enumerasi dalam satu file dapat menambahkan kopling yang tidak perlu, dan bahwa menyediakan enum yang digabungkan dalam satu paket dapat memperjelas penggunaan yang dimaksudkan dan memungkinkan kita untuk mengisolasi kode kopling yang sebenarnya itu sendiri sebagai Sebisa mungkin, solusi ideal adalah mendefinisikan setiap enumerasi secara terpisah, dan menyediakan paket bersama untuk enum yang dimaksudkan untuk sering digunakan bersama. Satu-satunya enum yang didefinisikan dalam file yang sama akan menjadi yang secara intrinsik terhubung bersama, sehingga penggunaan salah satu mengharuskan penggunaan yang lain juga.

// 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"
Justin Time - Pasang kembali Monica
sumber