Oddzielenie
Ostatecznie chodzi o oddzielenie ode mnie pod koniec dnia na najbardziej podstawowym poziomie projektowania, pozbawionym niuansów charakterystycznych dla naszych kompilatorów i łączników. Mam na myśli to, że możesz zrobić takie rzeczy, aby każdy nagłówek definiował tylko jedną klasę, używaj pimplów, deklaracji przekazywania do typów, które wymagają tylko zadeklarowania, nie są zdefiniowane, może nawet użyj nagłówków, które zawierają tylko deklaracje przekazywania (np .:) <iosfwd>
, jeden nagłówek na plik źródłowy , konsekwentnie organizuj system w oparciu o rodzaj deklarowanej / definiowanej rzeczy itp.
Techniki zmniejszania „zależności czasu kompilacji”
I niektóre techniki mogą nieco pomóc, ale możesz się wyczerpać tymi praktykami, a przecież przeciętny plik źródłowy w twoim systemie potrzebuje dwustronicowej preambuły #include
dyrektywy, aby zrobić coś nieznacznie znaczącego z niebotycznymi czasami kompilacji, jeśli zbytnio skupiasz się na zmniejszaniu zależności czasu kompilacji na poziomie nagłówka bez zmniejszania logicznych zależności w projektach interfejsów, i chociaż nie można tego uważać za „nagłówki spaghetti” ściśle mówiąc, ja Nadal powiedziałbym, że przekłada się to na podobne szkodliwe problemy, jak wydajność w praktyce. Na koniec dnia, jeśli Twoje jednostki kompilacyjne nadal wymagają dużej ilości informacji, aby były widoczne, aby cokolwiek zrobić, to przełoży się to na wydłużenie czasu kompilacji i zwielokrotnienie powodów, dla których musisz potencjalnie wrócić i zmienić rzeczy podczas tworzenia programistów czują się, jakby próbowali skończyć system, próbując po prostu zakończyć codzienne kodowanie. To'
Możesz na przykład sprawić, aby każdy podsystem zapewniał jeden bardzo abstrakcyjny plik nagłówka i interfejs. Ale jeśli podsystemy nie są od siebie oddzielone, to dostajesz znowu coś przypominającego spaghetti z interfejsami podsystemów w zależności od innych interfejsów podsystemów z wykresem zależności, który wygląda jak bałagan, aby działać.
Przekazywanie deklaracji do typów zewnętrznych
Ze wszystkich technik, które wyczerpałem, starając się uzyskać dawną bazę kodu, której skompilowanie zajęło dwie godziny, podczas gdy programiści czasami czekali 2 dni na swoją kolej w CI na naszych serwerach kompilacji (możesz sobie wyobrazić te maszyny do budowania jako wyczerpane bestie obciążone gorączkowo próbujące aby nadążyć i ponieść porażkę, gdy programiści wprowadzają zmiany), najbardziej wątpliwe było dla mnie zadeklarowanie typów zdefiniowanych w innych nagłówkach. Udało mi się sprowadzić ten kod źródłowy do około 40 minut po wiekach robienia tego w niewielkich krokach, próbując zmniejszyć „spaghetti z nagłówkiem”, najbardziej wątpliwą praktykę z perspektywy czasu (ponieważ powodując, że tracę z oczu podstawową naturę podczas projektowania tunelowego na współzależności nagłówków) było zadeklarowane do przodu typy zdefiniowane w innych nagłówkach.
Jeśli wyobrażasz sobie Foo.hpp
nagłówek, który ma coś takiego:
#include "Bar.hpp"
I używa tylko Bar
w nagłówku sposobu, który wymaga deklaracji, a nie definicji. to może wydawać się oczywiste, aby zadeklarować, class Bar;
aby uniknąć uczynienia definicji Bar
widocznej w nagłówku. Z wyjątkiem sytuacji, w której często praktykujesz, że większość jednostek kompilacji, które używają, Foo.hpp
nadal musi Bar
zostać zdefiniowana z dodatkowym obciążeniem związanym z koniecznością umieszczenia Bar.hpp
się na nich Foo.hpp
, lub napotykasz inny scenariusz, w którym to naprawdę pomaga i 99 % twoich jednostek kompilacyjnych może działać bez uwzględnienia Bar.hpp
, z tym wyjątkiem, że rodzi to bardziej fundamentalne pytanie projektowe (a przynajmniej myślę, że powinno to być w dzisiejszych czasach), dlaczego muszą zobaczyć deklarację Bar
i dlaczegoFoo
nawet trzeba się niepokoić, aby wiedzieć o tym, jeśli jest to nieistotne dla większości przypadków użycia (po co obciążać projekt zależnością od innego, rzadko używanego?).
Ponieważ koncepcyjnie tak naprawdę nie oddzieliliśmy się Foo
od Bar
. Właśnie to zrobiliśmy, aby nagłówek Foo
nie potrzebował tyle informacji na temat nagłówka Bar
, a to nie jest tak znaczące jak projekt, który naprawdę sprawia, że te dwa są całkowicie niezależne od siebie.
Skrypty osadzone
To jest naprawdę w przypadku baz kodowych na większą skalę, ale inną techniką, którą uważam za niezwykle przydatną, jest użycie wbudowanego języka skryptowego dla przynajmniej najbardziej wysokiego poziomu części twojego systemu. Odkryłem, że byłem w stanie osadzić Luę w ciągu jednego dnia i umożliwić jej jednolite wywoływanie wszystkich poleceń w naszym systemie (na szczęście polecenia były abstrakcyjne). Niestety natknąłem się na przeszkodę, w której deweloperzy nie ufali wprowadzeniu innego języka i, być może najdziwniej, z występem jako największym podejrzeniem. Mimo że mogłem zrozumieć inne obawy, wydajność nie powinna stanowić problemu, jeśli używamy skryptu tylko do wywoływania poleceń, gdy użytkownicy klikają przyciski, na przykład, które nie wykonują własnych dużych pętli (co próbujemy zrobić, martwisz się o nanosekundowe różnice w czasach odpowiedzi dla kliknięcia przycisku?).
Przykład
Tymczasem najbardziej efektywnym sposobem, jaki kiedykolwiek widziałem po wyczerpujących technikach skracających czas kompilacji w dużych bazach kodowych, są architektury, które rzeczywiście zmniejszają ilość informacji potrzebnych do działania jakiejkolwiek rzeczy w systemie, a nie tylko oddzielanie jednego nagłówka od drugiego od kompilatora perspektywa, ale wymagająca od użytkowników tych interfejsów robienia tego, co muszą, wiedząc (zarówno z punktu widzenia człowieka, jak i kompilatora, prawdziwe oddzielenie, które wykracza poza zależności kompilatora), absolutne minimum.
ECS to tylko jeden przykład (i nie sugeruję, abyś go używał), ale napotkanie go pokazało mi, że możesz mieć naprawdę epickie podstawy kodowe, które wciąż budują się zaskakująco szybko, z radością wykorzystując szablony i wiele innych dodatków, ponieważ ECS, dzięki natura tworzy bardzo odsprzężoną architekturę, w której systemy muszą tylko wiedzieć o bazie danych ECS i zwykle tylko garść typów komponentów (czasami tylko jeden), aby wykonać swoje zadanie:
Projektowanie, projektowanie, projektowanie
I tego rodzaju odsprzężone projekty architektoniczne na ludzkim poziomie koncepcyjnym są bardziej skuteczne pod względem minimalizacji czasów kompilacji niż którakolwiek z technik, które zbadałem powyżej, gdy twoja baza kodu rośnie i rośnie, ponieważ wzrost ten nie przekłada się na twoją średnią jednostka kompilacji zwielokrotniająca ilość informacji potrzebnych podczas kompilacji i czasy linków do pracy (każdy system, który wymaga od przeciętnego programisty uwzględnienia mnóstwa rzeczy do zrobienia czegokolwiek, również ich wymaga, a nie tylko kompilator wiedzieć o dużej ilości informacji, aby cokolwiek zrobić ). Ma również więcej zalet niż skrócenie czasu kompilacji i rozplątywanie nagłówków, ponieważ oznacza to również, że programiści nie muszą wiedzieć dużo o systemie poza tym, co jest od razu potrzebne, aby coś z nim zrobić.
Jeśli, na przykład, możesz zatrudnić eksperta w dziedzinie fizyki, aby opracował silnik fizyki dla Twojej gry AAA, który obejmuje miliony LOC, a on może zacząć bardzo szybko, znając absolutnie absolutną minimalną informację w zakresie dostępnych rodzajów i interfejsów jak również koncepcje systemu, to oczywiście przełoży się na zmniejszenie ilości informacji zarówno dla niego, jak i kompilatora, aby wymagało zbudowania silnika fizyki, a także przekłada się na znaczne skrócenie czasu kompilacji, generalnie sugerując, że nie ma nic przypominającego spaghetti w dowolnym miejscu w systemie. I właśnie to sugeruję, aby nadać priorytet wszystkim tym technikom: w jaki sposób projektujesz swoje systemy. Wyczerpanie innych technik będzie wisienką na górze, jeśli zrobisz to, w przeciwnym razie,