Entkopplung
Es geht mir letztendlich darum, mich am Ende des Tages auf der grundlegendsten Designebene zu entkoppeln, ohne die Nuance der Eigenschaften unserer Compiler und Linker. Ich meine, Sie können beispielsweise festlegen, dass jeder Header nur eine Klasse definiert, Pimpls verwendet, Deklarationen an Typen weiterleitet, die nur deklariert werden müssen, nicht definiert, oder sogar Header verwenden, die nur Forward-Deklarationen enthalten (z. B. <iosfwd>
), einen Header pro Quelldatei , organisieren Sie das System konsistent basierend auf der Art der deklarierten / definierten Dinge usw.
Techniken zum Reduzieren von "Abhängigkeiten bei der Kompilierung"
Und einige der Techniken können einiges helfen, aber Sie können feststellen, dass diese Praktiken anstrengend sind und Ihre durchschnittliche Quelldatei in Ihrem System immer noch eine zweiseitige Präambel benötigt #include
Anweisungen, die mit schnellen Erstellungszeiten nichts Sinnvolles anstellen, wenn Sie sich zu sehr auf die Reduzierung der Kompilierungszeitabhängigkeiten auf Kopfzeilenebene konzentrieren, ohne die logischen Abhängigkeiten in Ihren Schnittstellendesigns zu reduzieren, und obwohl dies streng genommen nicht als "Spaghetti-Kopfzeilen" betrachtet werden kann Ich würde immer noch sagen, dass dies zu ähnlichen nachteiligen Auswirkungen auf die Produktivität in der Praxis führt. Am Ende des Tages, wenn Ihre Kompilierungseinheiten immer noch eine Schiffsladung von Informationen benötigen, um sichtbar zu sein, wird dies zu einer Erhöhung der Build-Zeiten führen und die Gründe multiplizieren, die Sie haben, um möglicherweise zurück zu gehen und Dinge zu ändern, während Sie Entwickler machen Ich habe das Gefühl, als würden sie das System einfach überraschen, um ihre tägliche Programmierung zu beenden. Es'
Sie können beispielsweise festlegen, dass jedes Subsystem eine sehr abstrakte Header-Datei und Schnittstelle bereitstellt. Wenn die Subsysteme jedoch nicht voneinander entkoppelt sind, erhalten Sie wieder etwas Ähnliches wie Spaghetti mit Subsystemschnittstellen, abhängig von anderen Subsystemschnittstellen mit einem Abhängigkeitsdiagramm, das wie ein Durcheinander aussieht, um zu funktionieren.
Deklarationen an externe Typen weiterleiten
Von all den Techniken, die ich erschöpft habe, um zu versuchen, eine frühere Codebasis zu erhalten, die zwei Stunden in Anspruch nahm, während die Entwickler manchmal zwei Tage auf ihren Einsatz bei CI auf unseren Build-Servern warteten Um Schritt zu halten und Fehler zu machen, während Entwickler ihre Änderungen vorantreiben, war es für mich am fraglichsten, Typen zu deklarieren, die in anderen Headern definiert sind. Und ich habe es geschafft, diese Codebasis in kleinen Schritten auf ungefähr 40 Minuten zu reduzieren, während ich versucht habe, "Header - Spaghetti" zu reduzieren, was im Nachhinein die fragwürdigste Praxis ist (da ich die grundlegende Natur von aus den Augen verlor) Das Design, während der Tunnelblick auf Header-Abhängigkeiten gerichtet war, deklarierte Typen, die in anderen Headern definiert waren.
Wenn Sie sich einen Foo.hpp
Header vorstellen, der ungefähr so aussieht:
#include "Bar.hpp"
Und es verwendet Bar
im Header nur eine Art und Weise, die eine Deklaration erfordert, keine Definition. Dann scheint es ein Kinderspiel zu sein, zu deklarieren class Bar;
, um zu vermeiden, dass die Definition von Bar
in der Kopfzeile sichtbar wird. Außer in der Praxis oft werden Sie entweder die meisten der Kompilierungseinheiten finden, verwenden Foo.hpp
immer noch am Ende brauchen Bar
sowieso definiert werden mit der zusätzlichen Last, enthalten Bar.hpp
sich auf der Oberseite Foo.hpp
, oder Sie laufen in ein anderes Szenario , in dem die wirklich Hilfe tut und 99 % Ihrer Kompilierungseinheiten können ohne Einschluss arbeiten Bar.hpp
, außer dann wirft sie die grundlegendere Entwurfsfrage auf (oder zumindest denke ich, dass dies heutzutage der Fall sein sollte), warum sie überhaupt die Deklaration von Bar
und warum sehen müssenFoo
Man muss sich sogar die Mühe machen, davon zu erfahren, wenn es für die meisten Anwendungsfälle irrelevant ist (warum sollte man ein Design mit Abhängigkeiten zu einem anderen belasten, das kaum jemals verwendet wurde?).
Weil konzeptionell haben wir nicht wirklich entkoppelt Foo
von Bar
. Wir haben es gerade so gemacht, dass der Header von Foo
nicht so viele Informationen über den Header von benötigt Bar
, und das ist bei weitem nicht so aussagekräftig wie ein Design, das diese beiden wirklich völlig unabhängig voneinander macht.
Embedded Scripting
Dies ist wirklich für größere Codebasen gedacht, aber eine andere Technik, die ich als äußerst nützlich empfinde, ist die Verwendung einer eingebetteten Skriptsprache für mindestens die übergeordneten Teile Ihres Systems. Ich fand heraus, dass ich Lua in einen Tag einbetten und alle Befehle in unserem System einheitlich aufrufen konnte (die Befehle waren zum Glück abstrakt). Leider bin ich auf eine Straßensperre gestoßen, bei der die Entwickler der Einführung einer anderen Sprache misstrauten und, vielleicht am bizarrsten, Leistung als größten Verdacht empfanden. Obwohl ich andere Bedenken verstehen konnte, sollte die Leistung kein Problem sein, wenn wir das Skript nur zum Aufrufen von Befehlen verwenden, wenn Benutzer beispielsweise auf Schaltflächen klicken, die selbst keine großen Schleifen ausführen (was versuchen wir zu tun, Sorgen Sie sich über Nanosekunden-Unterschiede in den Reaktionszeiten bei einem Tastenklick?).
Beispiel
Der effektivste Weg, den ich je gesehen habe, nachdem ich Techniken zur Verkürzung der Kompilierungszeiten in großen Codebasen angewendet habe, sind Architekturen, die die Menge an Informationen, die für das Funktionieren einer Sache im System erforderlich sind, wirklich reduzieren, und nicht nur das Entkoppeln eines Headers von einem anderen von einem Compiler Die Benutzer dieser Schnittstellen müssen jedoch das Nötigste tun, um zu wissen, was sie tun müssen (sowohl vom menschlichen Standpunkt als auch vom Standpunkt des Compilers aus, echte Entkopplung, die über Compiler-Abhängigkeiten hinausgeht).
Das ECS ist nur ein Beispiel (und ich schlage nicht vor, dass Sie eines verwenden), aber als ich es sah, konnte ich feststellen, dass Sie einige wirklich epische Codebasen haben können, die sich immer noch überraschend schnell aufbauen lassen, während Sie Vorlagen und viele andere Extras verwenden, weil das ECS Natur schafft eine sehr entkoppelte Architektur, in der Systeme nur über die ECS-Datenbank Bescheid wissen müssen und in der Regel nur eine Handvoll Komponententypen (manchmal nur eine), um ihre Aufgabe zu erfüllen:
Design, Design, Design
Und diese Art von entkoppelten Architekturentwürfen auf menschlicher, konzeptioneller Ebene ist effektiver im Hinblick auf die Minimierung der Kompilierzeiten als alle Techniken, die ich oben untersucht habe, da Ihre Codebasis wächst und wächst und wächst, da sich dieses Wachstum nicht in Ihrem Durchschnitt niederschlägt Kompilierungseinheit, die die zum Zeitpunkt der Kompilierung und Verknüpfung für die Arbeit erforderlichen Mengeninformationen multipliziert (bei jedem System, bei dem ein durchschnittlicher Entwickler eine Schiffsladung von Dingen einbeziehen muss, um etwas zu tun, müssen auch diese Informationen vorhanden sein, und nicht nur der Compiler muss über eine Vielzahl von Informationen Bescheid wissen, um etwas zu tun ). Es hat auch mehr Vorteile als reduzierte Erstellungszeiten und das Entwirren von Headern, da Ihre Entwickler nicht viel über das System wissen müssen, was unmittelbar erforderlich ist, um etwas damit zu tun.
Wenn Sie zum Beispiel einen erfahrenen Physikentwickler beauftragen, eine Physik-Engine für Ihr AAA-Spiel zu entwickeln, die Millionen von LOC umfasst, und der sehr schnell mit der Kenntnis des absoluten Minimums an verfügbaren Informationen wie Typen und Schnittstellen beginnen kann Neben Ihren Systemkonzepten bedeutet dies natürlich auch, dass sowohl er als auch der Compiler weniger Informationen benötigen, um seine Physik-Engine zu erstellen. Gleichzeitig bedeutet dies eine erhebliche Verkürzung der Build-Zeiten und im Allgemeinen, dass es nichts gibt, was Spaghetti ähnelt überall im System. Und das ist es, was ich vorschlage, um all diesen anderen Techniken Vorrang einzuräumen: wie Sie Ihre Systeme entwerfen. Erschöpfende andere Techniken werden das i-Tüpfelchen sein, wenn Sie es tun, während, sonst