Jak mogę zapobiec piekłu nagłówka?

44

Rozpoczynamy nowy projekt od zera. Około ośmiu programistów, kilkanaście podsystemów, każdy z czterema lub pięcioma plikami źródłowymi.

Co możemy zrobić, aby zapobiec „nagłówkowi piekła”, AKA „nagłówkom spaghetti”?

  • Jeden nagłówek na plik źródłowy?
  • Plus jeden na podsystem?
  • Oddzielić typdefy, kable i wyliczenia od prototypów funkcji?
  • Oddzielić wewnętrzny podsystem od zewnętrznych elementów podsystemu?
  • Czy nalegasz, aby każdy pojedynczy plik, niezależnie od tego, czy nagłówek, czy źródło musiał być samodzielny, może zostać skompilowany?

Nie proszę o „najlepszy” sposób, tylko wskazówkę, na co uważać i co może powodować smutek, abyśmy mogli spróbować tego uniknąć.

To będzie projekt w C ++, ale informacje w C pomogłyby przyszłym czytelnikom.

Mawg
źródło
16
Zdobądź kopię projektu oprogramowania w dużej skali C ++ , nie tylko nauczy to, jak unikać problemów z nagłówkami, ale także wiele innych problemów dotyczących fizycznych zależności między plikami źródłowymi i obiektowymi w projekcie C ++.
Doc Brown
6
Wszystkie odpowiedzi tutaj są świetne. Chciałem dodać, że dokumentacja dotycząca używania obiektów, metod, funkcji powinna znajdować się w plikach nagłówkowych. Nadal widzę doc w plikach źródłowych. Nie każ mi czytać źródła. To jest punkt pliku nagłówkowego. Nie powinienem czytać źródła, chyba że jestem implementatorem.
Bill Door
1
Jestem pewien, że wcześniej z tobą współpracowałem. Często :-(
Mawg
5
To, co opisujesz, nie jest dużym projektem. Dobry projekt jest zawsze mile widziany, ale możesz nigdy nie spotkać się z problemami „dużych systemów”.
Sam
2
Boost faktycznie ma podejście obejmujące wszystko. Każda pojedyncza funkcja ma własny plik nagłówka, ale każdy większy moduł ma również nagłówek, który zawiera wszystko. Okazuje się, że jest to naprawdę potężne narzędzie do minimalizowania piekła nagłówka bez zmuszania Cię do #include kilkuset plików za każdym razem.
Cort Ammon

Odpowiedzi:

39

Prosta metoda: jeden nagłówek na plik źródłowy. Jeśli masz kompletny podsystem, w którym użytkownicy nie powinni wiedzieć o plikach źródłowych, przygotuj jeden nagłówek podsystemu wraz ze wszystkimi wymaganymi plikami nagłówkowymi.

Każdy plik nagłówka powinien być kompilowany samodzielnie (lub powiedzmy, że plik źródłowy, w tym pojedynczy nagłówek, powinien się skompilować). Boli mnie to, że znalazłem, który plik nagłówkowy zawiera to, co chcę, a następnie muszę wyśledzić inne pliki nagłówkowe. Prostym sposobem na wymuszenie tego jest umieszczenie każdego pliku źródłowego w pierwszej kolejności pliku nagłówka (dzięki doug65536, myślę, że robię to przez większość czasu, nawet nie zdając sobie z tego sprawy).

Upewnij się, że korzystasz z dostępnych narzędzi, aby skrócić czas kompilacji - każdy nagłówek musi być dołączony tylko raz, użyj prekompilowanych nagłówków, aby skrócić czas kompilacji, w miarę możliwości użyj wstępnie skompilowanych modułów, aby skrócić czas kompilacji.

gnasher729
źródło
Trudne jest wywołanie funkcji między podsystemami, z parametrami typów zadeklarowanymi w innym podsystemie.
Mawg
6
Podstępne czy nie, „#include <subsystem1.h>” powinno się skompilować. Jak to osiągniesz, zależy od ciebie. @FrankPuffer: Dlaczego?
gnasher729
13
@Mawg Oznacza to, że potrzebujesz osobnego, współużytkowanego podsystemu, który zawiera cechy wspólne różnych podsystemów, lub potrzebujesz uproszczonych nagłówków „interfejsu” dla każdego podsystemu (który jest następnie używany przez nagłówki implementacji, zarówno wewnętrzne, jak i międzysystemowe) . Jeśli nie można napisać nagłówków interfejsu bez uwzględnienia wielu elementów, projekt podsystemu jest zawalony i trzeba przeprojektować różne elementy, aby podsystemy były bardziej niezależne. (Który może obejmować wyciągnięcie wspólnego podsystemu jako trzeciego modułu.)
RM
8
Dobrą techniką zapewniającą niezależność nagłówka jest reguła, że ​​plik źródłowy zawsze zawiera najpierw własny nagłówek . Spowoduje to wychwycenie przypadków, w których trzeba przenieść zależność z pliku implementacji do pliku nagłówka.
doug65536
4
@FrankPuffer: Nie usuwaj swoich komentarzy, zwłaszcza jeśli inni na nie odpowiedzą, ponieważ sprawia, że ​​odpowiedzi są bez kontekstu. Zawsze możesz poprawić swoje oświadczenie w nowym komentarzu. Dzięki! Chciałbym wiedzieć, co faktycznie powiedziałeś, ale teraz go nie ma :(
MPW
18

Zdecydowanie najważniejszym wymogiem jest zmniejszenie zależności między plikami źródłowymi. W C ++ często stosuje się jeden plik źródłowy i jeden nagłówek na klasę. Dlatego jeśli masz dobry projekt klasy, nawet nie zbliżysz się do piekła.

Możesz także spojrzeć na to odwrotnie: jeśli masz już piekło nagłówka w swoim projekcie, możesz być całkiem pewien, że należy ulepszyć projekt oprogramowania.

Aby odpowiedzieć na konkretne pytania:

  • Jeden nagłówek na plik źródłowy? → Tak, w większości przypadków działa to dobrze i ułatwia znajdowanie rzeczy. Ale nie róbcie z tego religii.
  • Plus jeden na podsystem? → Nie, dlaczego chcesz to zrobić?
  • Oddzielić typdefy, kable i wyliczenia od prototypów funkcji? → Nie, funkcje i powiązane typy należą do siebie.
  • Oddzielić wewnętrzny podsystem od zewnętrznych elementów podsystemu? → Tak, oczywiście. Zmniejszy to zależności.
  • Nalegać, aby każdy pojedynczy plik, niezależnie od tego, czy nagłówek czy źródło przez standalones był zgodny? → Tak, nigdy nie wymagaj dołączania żadnego nagłówka przed innym nagłówkiem.
Frank Puffer
źródło
12

Oprócz innych zaleceń, podobnie jak ograniczenie zależności (dotyczy głównie C ++):

  1. Uwzględnij tylko to, czego naprawdę potrzebujesz, tam gdzie jest to potrzebne (najniższy możliwy poziom). Np. nie dołączaj do nagłówka, jeśli potrzebujesz połączeń tylko w źródle.
  2. W miarę możliwości używaj deklaracji przesyłania w nagłówkach (nagłówek zawiera tylko wskaźniki lub odwołania do innych klas).
  3. Wyczyść dołączenia po każdym refaktoryzacji (skomentuj je, zobacz, gdzie kompilacja kończy się niepowodzeniem, przenieś je tam, usuń wciąż komentowane wiersze dołączania).
  4. Nie pakuj zbyt wielu typowych funkcji do tego samego pliku; podzielić je według funkcjonalności (np. Logger to jedna klasa, a zatem jeden nagłówek i jeden plik źródłowy; SystemHelper dito. itp.).
  5. Trzymaj się zasad OO, nawet jeśli wszystko, co dostajesz, to klasa składająca się wyłącznie z metod statycznych (zamiast samodzielnych funkcji) - lub zamiast tego użyj przestrzeni nazw .
  6. W przypadku niektórych typowych udogodnień wzorzec singletonu jest raczej przydatny, ponieważ nie trzeba żądać wystąpienia od innego, niezwiązanego obiektu.
Murphy
źródło
5
Na # 3 narzędzie może pomóc w tym, czego używasz , unikając ręcznej ponownej kompilacji metodą zgadywania i sprawdzania.
RM
1
Czy możesz wyjaśnić, jakie korzyści płyną z singletonów w tym kontekście? Naprawdę tego nie rozumiem.
Frank Puffer
@FrankPuffer Moje uzasadnienie jest takie: bez singletona niektóre instancje zwykle posiadają instancje klasy pomocniczej, jak Logger. Jeśli trzecia klasa chce z niej skorzystać, musi poprosić właściciela o odwołanie do klasy pomocniczej, co oznacza, że ​​używasz dwóch klas i oczywiście dołączasz ich nagłówki - nawet jeśli użytkownik nie miałby nic wspólnego z właścicielem. W przypadku singletona wystarczy dołączyć nagłówek klasy pomocnika i można bezpośrednio zażądać instancji. Czy widzisz błąd w tej logice?
Murphy
1
# 2 (deklaracje forward) może mieć ogromną różnicę w czasie kompilacji i sprawdzaniu zależności. Jak pokazuje ta odpowiedź ( stackoverflow.com/a/9999752/509928 ), dotyczy ona zarówno C ++, jak i C
Dave Compton
3
Możesz również używać deklaracji przekazywania przy przekazywaniu według wartości, pod warunkiem, że funkcja nie jest zdefiniowana bezpośrednio. Deklaracja funkcji nie jest kontekstem, w którym potrzebna jest pełna definicja typu (definicja funkcji lub wywołanie funkcji jest takim kontekstem).
StoryTeller - Unslander Monica
6

Jeden nagłówek na plik źródłowy, który określa, co plik źródłowy implementuje / eksportuje.

Tyle plików nagłówka, ile potrzeba, zawartych w każdym pliku źródłowym (zaczynając od własnego nagłówka).

Unikaj dołączania (minimalizuj dołączanie) plików nagłówkowych do innych plików nagłówkowych (aby uniknąć zależności cyklicznych). Aby uzyskać szczegółowe informacje, zapoznaj się z odpowiedzią na pytanie „czy dwie klasy widzą się za pomocą C ++?”

Jest cała książka na ten temat, Large-Scale C ++ Software Design autorstwa Lakos. Opisuje posiadanie „warstw” oprogramowania: warstwy wysokiego poziomu używają warstw niższego poziomu, a nie odwrotnie, co ponownie pozwala uniknąć zależności cyklicznych.

ChrisW
źródło
4

Twierdzę, że twoje pytanie jest zasadniczo niemożliwe do odpowiedzi, ponieważ istnieją dwa rodzaje piekła nagłówka:

  • Tego rodzaju, w którym musisz uwzględnić milion różnych nagłówków, a kto do diabła może je wszystkie zapamiętać? I utrzymujesz te listy nagłówków? Ugh.
  • Taki, w którym umieszczasz jedną rzecz i dowiadujesz się, że zawarłeś całą Wieżę Babel (czy powinienem powiedzieć Wieża Wieży?)

chodzi o to, że jeśli spróbujesz uniknąć tego pierwszego, do pewnego stopnia skończysz z tym drugim i na odwrót.

Istnieje również trzeci rodzaj piekła, którym są zależności kołowe. Mogą pojawić się, jeśli nie będziesz ostrożny ... unikanie ich nie jest bardzo skomplikowane, ale musisz poświęcić trochę czasu, aby pomyśleć o tym, jak to zrobić. Zobacz wykład Johna Lakosa na temat poziomowania w CppCon 2016 (lub tylko slajdy ).

einpoklum - przywróć Monikę
źródło
1
Nie zawsze można uniknąć zależności cyklicznych. Przykładem jest model, w którym podmioty odnoszą się do siebie. Przynajmniej możesz spróbować ograniczyć cykliczność w podsystemie, co oznacza, że ​​jeśli dodasz nagłówek podsystemu, wyrejestrujesz cykliczność.
dokładnie
2
@nalply: Miałem na myśli unikanie cyklicznej zależności nagłówków, a nie kodu ... jeśli nie unikniesz cyklicznej zależności nagłówków, prawdopodobnie nie będziesz w stanie zbudować. Ale tak, punkt zajęty, +1.
einpoklum
1

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 #includedyrektywy, 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.hppnagłówek, który ma coś takiego:

#include "Bar.hpp"

I używa tylko Barw nagłówku sposobu, który wymaga deklaracji, a nie definicji. to może wydawać się oczywiste, aby zadeklarować, class Bar;aby uniknąć uczynienia definicji Barwidocznej w nagłówku. Z wyjątkiem sytuacji, w której często praktykujesz, że większość jednostek kompilacji, które używają, Foo.hppnadal musi Barzostać zdefiniowana z dodatkowym obciążeniem związanym z koniecznością umieszczenia Bar.hppsię 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ę Bari 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ę Foood Bar. Właśnie to zrobiliśmy, aby nagłówek Foonie 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:

wprowadź opis zdjęcia tutaj

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,

Dragon Energy
źródło
1
Doskonała odpowiedź! Althoguh Musiałem trochę wykopać, aby dowiedzieć się, co to pimpls :-)
Mawg
0

To kwestia opinii. Zobacz odpowiedź i że jeden. I zależy to również od wielkości projektu (jeśli uważasz, że w swoim projekcie będziesz mieć miliony linii źródłowych, to nie jest tak samo, jak posiadanie kilkudziesięciu tysięcy).

W przeciwieństwie do innych odpowiedzi, zalecam jeden (raczej duży) nagłówek publiczny na podsystem (który może zawierać nagłówki „prywatne”, być może posiadające osobne pliki do implementacji wielu wbudowanych funkcji). Można nawet rozważyć nagłówek mający tylko kilka #include dyrektyw.

Nie sądzę, że zaleca się wiele plików nagłówkowych. W szczególności nie polecam jednego pliku nagłówkowego na klasę ani wielu małych plików nagłówkowych zawierających po kilkadziesiąt linii.

(Jeśli masz dużą liczbę małych plików, musisz umieścić wiele z nich w każdej małej jednostce tłumaczeniowej , a ogólny czas kompilacji może ucierpieć)

Naprawdę chcesz zidentyfikować, dla każdego podsystemu i pliku, głównego programistę odpowiedzialnego za to.

Wreszcie w przypadku małego projektu (np. Zawierającego mniej niż sto tysięcy wierszy kodu źródłowego) nie jest to bardzo ważne. Podczas projektu będziesz w stanie z łatwością refaktoryzować kod i zreorganizować go w różne pliki. Będziesz po prostu kopiować i wklejać fragmenty kodu do nowych plików (nagłówka), co nie jest wielkim problemem (trudniejsze jest mądrze zaprojektować sposób reorganizacji plików, i to jest specyficzne dla projektu).

(moje osobiste preferencje to unikanie zbyt dużych i zbyt małych plików; często mam pliki źródłowe zawierające kilka tysięcy wierszy każdy; i nie boję się pliku nagłówkowego - w tym wbudowanych definicji funkcji - zawierającego setki wierszy lub nawet kilku tysiące z nich)

Zauważ, że jeśli chcesz używać prekompilowanych nagłówków z GCC (co czasem jest rozsądnym podejściem do krótszego czasu kompilacji), potrzebujesz jednego pliku nagłówka (w tym wszystkich pozostałych, a także nagłówków systemowych).

Zauważ, że w C ++ standardowe pliki nagłówkowe pobierają dużo kodu . Na przykład #include <vector>ciągnie ponad dziesięć tysięcy linii na moim GCC 6 w systemie Linux (18100 linii). I #include <map> rozwija się do prawie 40KLOC. Dlatego, jeśli masz wiele małych plików nagłówkowych, w tym standardowych nagłówków, kończysz parsowanie wielu tysięcy wierszy podczas kompilacji, a twój czas kompilacji cierpi. Dlatego nie lubię mieć wielu małych linii źródłowych C ++ (najwyżej kilkuset wierszy), ale wolę mieć mniej, ale większe pliki C ++ (kilka tysięcy wierszy).

(tak więc posiadanie setek małych plików C ++, które zawsze zawierają - nawet pośrednio - kilka standardowych plików nagłówkowych, daje ogromny czas kompilacji, co denerwuje programistów)

W kodzie C dość często pliki nagłówkowe rozwijają się do czegoś mniejszego, więc kompromis jest inny.

Zainspiruj się także wcześniejszą praktyką w istniejących projektach wolnego oprogramowania (np. Na github ).

Zauważ, że zależności można rozwiązać przy pomocy dobrego systemu automatyzacji kompilacji . Przestudiuj dokumentację marki GNU . Pamiętaj o różnych -Mflagach preprocesora dla GCC (przydatne do automatycznego generowania zależności).

Innymi słowy, twój projekt (z mniej niż setką plików i tuzinem programistów) prawdopodobnie nie jest wystarczająco duży, aby naprawdę zainteresować się „nagłówkiem piekła”, więc twoje obawy nie są uzasadnione . Możesz mieć tylko kilkanaście plików nagłówkowych (lub nawet znacznie mniej), możesz wybrać jeden plik nagłówkowy na jednostkę tłumaczeniową, możesz nawet wybrać jeden plik nagłówkowy, a cokolwiek wybierzesz, nie będzie „nagłówek piekła” (a refaktoryzacja i reorganizacja plików pozostałyby dość łatwe, więc początkowy wybór nie jest tak naprawdę ważny ).

(Nie skupiaj swoich wysiłków na „nagłówku piekła” - co nie jest dla ciebie problemem - ale skup je na zaprojektowaniu dobrej architektury)

Basile Starynkevitch
źródło
Wspomniane szczegóły techniczne mogą być poprawne. Jednak, tak jak to zrozumiałem, OP poprosił o wskazówki, jak poprawić konserwację i organizację kodu, a nie kompilować czas. Widzę bezpośredni konflikt między tymi dwoma celami.
Murphy
Ale to wciąż kwestia opinii. I PO najwidoczniej rozpoczyna niezbyt duży projekt.
Basile Starynkevitch