Jestem początkującym programistą gier, pracuję nad okazjonalnymi grami niezależnymi i od jakiegoś czasu robię coś, co na początku wydawało się złą praktyką, ale naprawdę chcę uzyskać odpowiedź od niektórych doświadczonych programistów tutaj.
Załóżmy, że mam plik o nazwie, w enumList.h
której deklaruję wszystkie wyliczenia, których chcę używać w mojej grze:
// 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
};
Główną ideą jest to, że deklaruję wszystkie wyliczenia w grze w 1 pliku , a następnie importuję ten plik, gdy muszę użyć określonego wyliczenia z niego, zamiast deklarować go w pliku, w którym muszę go użyć. Robię to, ponieważ sprawia, że wszystko jest czyste, mogę uzyskać dostęp do każdego wyliczenia w jednym miejscu zamiast otwierać strony wyłącznie w celu uzyskania dostępu do jednego wyliczenia.
Czy to zła praktyka i czy może w jakikolwiek sposób wpłynąć na wydajność?
źródło
materials_t
plików, które nie zajmują się materiałami, będziesz musiał go odbudować.enumList.h
służyć jako zbiór#include
s dla tych plików. Pozwala to plikom, które potrzebują tylko jednego wyliczenia, aby uzyskać go bezpośrednio, zapewniając jednocześnie osobny pakiet dla wszystkiego, co naprawdę chce wszystkich.Odpowiedzi:
Naprawdę uważam, że to zła praktyka. Kiedy mierzymy jakość kodu, coś nazywamy „szczegółowością”. Twoja ziarnistość poważnie cierpi, umieszczając wszystkie te wyliczenia w jednym pliku, a tym samym cierpi na łatwość konserwacji.
Miałem jeden plik na wyliczenie, aby szybko go znaleźć i pogrupować kodem behawioralnym określonej funkcji (np. Wyliczenie materiałów w folderze, w którym zachowano zachowanie materiału itp.);
Możesz myśleć, że jest czysty, ale tak naprawdę nie jest. Łączy rzeczy, które nie pasują do siebie pod względem funkcjonalności i modułów, i zmniejsza modułowość aplikacji. W zależności od wielkości bazy kodu i tego, jak modułowa ma być struktura, może to przekształcić się w większy problem i nieczytelny kod / zależności w innych częściach systemu. Jeśli jednak napiszesz mały, monolityczny system, nie musi to mieć zastosowania. Jednak nie zrobiłbym tego w ten sposób, nawet dla małego monolitycznego układu.
źródło
Tak, to zła praktyka, nie z powodu wydajności, ale z powodu łatwości konserwacji.
Sprawia, że rzeczy są „czyste” tylko w OCD „zbierają podobne rzeczy razem”. Ale tak naprawdę nie jest to użyteczny i dobry rodzaj „czystości”.
Jednostki kodu należy pogrupować, aby zmaksymalizować spójność i zminimalizować sprzężenie , co najlepiej osiągnąć, grupując je w zasadniczo niezależne moduły funkcjonalne. Pogrupowanie ich według kryteriów technicznych (takich jak zebranie wszystkich wyliczeń) daje odwrotną sytuację - łączy kod, który nie ma absolutnie żadnej powiązania funkcjonalnego, i umieszcza wyliczenia, których można użyć tylko w jednym miejscu, w innym pliku.
źródło
Cóż, to tylko dane (a nie zachowanie). Najgorsze, co może / teoretycznie mogłoby się zdarzyć, to włączenie tego samego kodu i skompilowanie go więcej niż jeden raz, tworząc stosunkowo większy program.
Jest po prostu niemożliwe, aby takie włączenie mogło dodać więcej cykli do twoich operacji, biorąc pod uwagę, że nie ma w nich żadnego kodu behawioralnego / proceduralnego (bez pętli, bez ifs itp.).
(Relatywnie) większy program z trudem może wpływać na wydajność (szybkość wykonywania), a w każdym razie jest tylko odległym problemem teoretycznym. Większość (być może wszystkie) kompilatory zarządzają zawiera w sposób, który zapobiega takim problemom.
IMHO, zalety, które zyskujesz dzięki jednemu plikowi (bardziej czytelny i łatwiejszy do zarządzania kod) znacznie przekraczają wszelkie możliwe wady.
źródło
Dla mnie wszystko zależy od zakresu twojego projektu. Jeśli jest jeden plik z, powiedzmy 10 strukturami, i są to jedyne używane kiedykolwiek, byłbym całkowicie wygodny, mając jeden plik .h. Jeśli masz kilka różnych rodzajów funkcjonalności, powiedzmy jednostki, ekonomię, budynki itp. Zdecydowanie podzieliłbym to. Utwórz jednostki. H, w którym znajdują się wszystkie struktury, które dotyczą jednostek. Jeśli chcesz coś zrobić z jednostkami gdzieś, musisz dołączyć jednostki. Pewnie, ale jest to również miły „identyfikator”, że coś z jednostkami prawdopodobnie jest zrobione w tym pliku.
Spójrz na to w ten sposób, nie kupujesz supermarketu, ponieważ potrzebujesz puszki coli;)
źródło
Nie jestem programistą C ++, więc ta odpowiedź będzie bardziej ogólna dla OOA i D:
Zazwyczaj obiekty kodu powinny być pogrupowane pod względem przydatności funkcjonalnej, niekoniecznie pod względem konstrukcji specyficznych dla języka. Kluczowym pytaniem typu „tak-nie”, które należy zawsze zadawać, jest: „czy koder końcowy będzie używał większości lub wszystkich obiektów, które otrzymają podczas korzystania z biblioteki?” Jeśli tak, zgrupuj się. Jeśli nie, rozważ podzielenie obiektów kodu i umieszczenie ich bliżej innych obiektów, które ich potrzebują (zwiększając w ten sposób szansę, że konsument będzie potrzebował wszystkiego, do czego faktycznie ma dostęp).
Podstawową koncepcją jest „wysoka spójność”; elementy kodu (od metod klasy aż po klasy w przestrzeni nazw lub bibliotece DLL i same biblioteki DLL) powinny być zorganizowane w taki sposób, aby koder mógł zawierać wszystko, czego potrzebuje, i nic, czego nie ma. To sprawia, że ogólny projekt jest bardziej tolerancyjny na zmiany; rzeczy, które muszą się zmienić, mogą pozostać bez wpływu na inne obiekty kodu, które nie musiały się zmieniać. W wielu okolicznościach sprawia to, że aplikacja jest bardziej wydajna pod względem pamięci; biblioteka DLL jest ładowana w całości do pamięci, niezależnie od tego, ile z tych instrukcji kiedykolwiek zostało wykonanych przez proces. Projektowanie aplikacji „lean” wymaga zatem zwrócenia uwagi na ilość kodu wczytywanego do pamięci.
Ta koncepcja ma zastosowanie na praktycznie wszystkich poziomach organizacji kodu, z różnym stopniem wpływu na łatwość konserwacji, wydajność pamięci, wydajność, szybkość kompilacji itp. Jeśli koder musi odwoływać się do nagłówka / biblioteki DLL o raczej monolitycznym rozmiarze tylko w celu uzyskania dostępu do jednego obiektu, który nie zależy od żadnego innego obiektu w tej bibliotece DLL, prawdopodobnie należy ponownie rozważyć włączenie tego obiektu do biblioteki DLL. Można jednak pójść zbyt daleko w drugą stronę; DLL dla każdej klasy jest złym pomysłem, ponieważ spowalnia szybkość kompilacji (więcej DLL do przebudowania z powiązanym narzutem) i sprawia, że przechowywanie wersji jest koszmarem.
Przykład: jeśli jakiekolwiek użycie biblioteki kodu w świecie rzeczywistym wymagałoby użycia większości lub wszystkich wyliczeń, które umieszczasz w tym pojedynczym pliku „enumerations.h”, to z całą pewnością zgrupuj je razem; będziesz wiedział, gdzie ich szukać. Ale jeśli koder zużywający może potrzebować tylko jednego lub dwóch z tuzinów wyliczeń, które podajesz w nagłówku, zachęcam do umieszczenia ich w osobnej bibliotece i uczynienia z niej zależności większej od reszty wyliczeń . Pozwala to programistom uzyskać tylko jeden lub dwa, których chcą, bez konieczności łączenia się z bardziej monolityczną biblioteką DLL.
źródło
Tego rodzaju sprawa staje się problemem, gdy wielu programistów pracuje na tej samej podstawie kodu.
Z pewnością widziałem, jak podobne pliki globalne stają się ogniwem łączącym kolizje i wszelkiego rodzaju żal przy (większych) projektach.
Jeśli jednak jako jedyny pracujesz nad projektem, powinieneś robić wszystko, co najbardziej ci odpowiada, i stosować „najlepsze praktyki” tylko wtedy, gdy rozumiesz (i zgadzasz się) z motywacją.
Lepiej popełniaj błędy i ucz się na nich, niż ryzykować utknięcie w praktykach programowania kultowego przez resztę życia.
źródło
Tak, złą praktyką jest robienie tego przy dużym projekcie. POCAŁUNEK.
Młody współpracownik zmienił nazwę prostej zmiennej w podstawowym pliku .h, a 100 inżynierów czekało 45 minut na przebudowanie wszystkich plików, co wpłynęło na wydajność wszystkich. ;)
Z biegiem lat wszystkie projekty zaczynają się od małych balonów, a my przeklinamy tych, którzy na wczesnych skrótach stworzyli dług techniczny. Najlepsze praktyki, ograniczaj globalną treść .h do tego, co niekoniecznie jest globalne.
źródło
W tym przypadku powiedziałbym, że idealną odpowiedzią jest to, że zależy to od tego, w jaki sposób używane są wyliczenia, ale w większości przypadków prawdopodobnie najlepiej jest zdefiniować wszystkie wyliczenia osobno, ale jeśli którakolwiek z nich jest już sprzężona projektowo, powinieneś podać środki wspólnego wprowadzania wspomnianych sprzężonych enum. W efekcie masz tolerancję sprzężenia do ilości już zamierzonego sprzężenia, ale już nie.
Biorąc to pod uwagę, najbardziej elastyczne rozwiązanie prawdopodobnie zdefiniuje każde wyliczenie w osobnym pliku, ale zapewni sprzężone pakiety, gdy będzie to uzasadnione (zgodnie z zamierzonym użyciem zaangażowanych wyliczeń).
Zdefiniowanie wszystkich wyliczeń w tym samym pliku powoduje ich połączenie, a przez rozszerzenie każdy kod zależny od jednego lub większej liczby wyliczeń zależy od wszystkich wyliczeń, niezależnie od tego, czy kod faktycznie używa innych wyliczeń.
renderMap()
wolałbym tylko wiedzieć o tymmap_t
, ponieważ w przeciwnym razie wszelkie zmiany w innych wpłyną na to, nawet jeśli tak naprawdę nie wchodzi w interakcje z innymi.Jednak w przypadku, gdy komponenty są już połączone ze sobą, zapewnienie wielu wyliczeń w jednym pakiecie może łatwo zapewnić dodatkową jasność i prostotę, pod warunkiem, że istnieje wyraźny logiczny powód, aby wyliczyć wyliczenia, że użycie tych wyliczeń jest również połączone, a zapewnienie ich nie wprowadza żadnych dodatkowych połączeń.
W takim przypadku nie ma bezpośredniego, logicznego połączenia między typem elementu a typem materiału (przy założeniu, że elementy nie są wykonane z jednego ze zdefiniowanych materiałów). Gdybyśmy jednak mieli przypadek, w którym np. Jedno wyliczenie jest wyraźnie zależne od drugiego, wówczas sensownym jest dostarczenie pojedynczego pakietu zawierającego wszystkie sprzężone wyliczenia (jak również wszelkie inne sprzężone elementy), aby połączenie mogło być izolowane do tego pakietu w jak największym stopniu.
Ale niestety ... nawet jeśli dwa wyliczenia są ze sobą wewnętrznie połączone, nawet jeśli jest to coś tak silnego, jak „drugi wylicznik zapewnia podkategorie dla pierwszego wyliczenia”, może jeszcze przyjść czas, w którym tylko jedna z wyliczeń jest konieczna.
Dlatego, ponieważ wiemy oba, że zawsze mogą wystąpić sytuacje, w których zdefiniowanie wielu wyliczeń w jednym pliku może dodać niepotrzebne sprzężenie, oraz że zapewnienie sprzężonych wyliczeń w jednym pakiecie może wyjaśnić zamierzone użycie i pozwolić nam wyodrębnić sam kod sprzężenia jako w miarę możliwości idealnym rozwiązaniem jest zdefiniowanie każdego wyliczenia osobno i zapewnienie wspólnych pakietów dla dowolnych wyliczeń, które mają być często używane razem. Jedynymi wyliczeniami zdefiniowanymi w tym samym pliku będą te, które są ze sobą wewnętrznie połączone, tak że użycie jednego z nich wymaga również użycia drugiego.
źródło