Развязка
В конечном счете, речь идет о развязке для меня на самом фундаментальном уровне проектирования, лишенном нюансов характеристик наших компиляторов и компоновщиков. Я имею в виду, что вы можете делать такие вещи, как сделать так, чтобы каждый заголовок определял только один класс, использовать pimpls, пересылать объявления типам, которые нужно только объявить, но не определить, возможно, даже использовать заголовки, которые просто содержат прямые объявления (например:) <iosfwd>
, один заголовок на исходный файл организовать систему последовательно, основываясь на типе декларируемой / определенной вещи и т. д.
Методы уменьшения "зависимостей времени компиляции"
И некоторые из методов могут немного помочь, но вы можете исчерпать эти методы и все же найти свой средний исходный файл в вашей системе, нуждающийся в двухстраничной преамбуле #include
указание делать что-то немного значимое со стремительно растущим временем сборки, если вы слишком много внимания уделяете сокращению зависимостей во время компиляции на уровне заголовка без уменьшения логических зависимостей в ваших интерфейсах, и хотя это, строго говоря, не может считаться «заголовками спагетти», я Я бы все еще сказал, что это приводит к подобным вредным проблемам производительности на практике. В конце концов, если ваши модули компиляции по-прежнему требуют, чтобы для выполнения чего-либо было достаточно информации, то это приведет к увеличению времени сборки и умножению причин, по которым вам придется вернуться назад и что-то менять, в то время как разработчики делают чувствую, что они бьют головой систему, просто пытаясь закончить свое ежедневное кодирование. Это'
Например, вы можете заставить каждую подсистему предоставлять один очень абстрактный заголовочный файл и интерфейс. Но если подсистемы не отделены друг от друга, то вы снова получаете нечто похожее на спагетти с интерфейсами подсистем, зависящими от других интерфейсов подсистем с графом зависимостей, который для работы выглядит как беспорядок.
Пересылка объявлений внешним типам
Из всех методов, которые я исчерпал, чтобы попытаться получить прежнюю кодовую базу, на создание которой ушло два часа, в то время как разработчики иногда ждали своей очереди в CI на наших серверах сборки (2 дня) (вы можете почти представить, что эти машины сборки истощены чудовищными усилиями, отчаянно пытаясь чтобы не отставать и терпеть неудачу, пока разработчики продвигают свои изменения), самым сомнительным для меня было прямое объявление типов, определенных в других заголовках. И мне удалось довести эту кодовую базу до 40 минут или около того после целого ряда этапов, которые делали это небольшими пошаговыми шагами, пытаясь уменьшить «заголовок спагетти», наиболее сомнительную практику в ретроспективе (например, заставляя меня упускать из виду фундаментальную природу проектирование, в то время как туннель рассматривал взаимозависимости заголовков), было прямым объявлением типов, определенных в других заголовках.
Если вы представляете Foo.hpp
заголовок, который имеет что-то вроде:
#include "Bar.hpp"
И он использует только Bar
в заголовке способ, который требует объявления, а не определения. тогда это может показаться легким делом, чтобы объявить, class Bar;
чтобы избежать определения Bar
видимого в заголовке. За исключением случаев, когда на практике часто вы либо обнаружите, что большинство используемых модулей компиляции Foo.hpp
все равно в конечном итоге нуждаются Bar
в определении с дополнительным бременем необходимости включать Bar.hpp
себя поверх Foo.hpp
, либо вы столкнетесь с другим сценарием, где это действительно помогает и % ваших модулей компиляции могут работать без включения Bar.hpp
, за исключением того, что это поднимает более фундаментальный вопрос проектирования (или, по крайней мере, я думаю, что это должно происходить в настоящее время), почему они должны даже видеть объявление Bar
и почемуFoo
даже нужно беспокоиться, чтобы узнать об этом, если это не имеет отношения к большинству случаев использования (зачем обременять дизайн зависимостями с другим, который едва когда-либо использовался?).
Потому что концептуально мы действительно не отделены Foo
от Bar
. Мы только что сделали это, чтобы заголовок Foo
не нуждался в таком большом количестве информации о заголовке Bar
, и это не так существенно, как дизайн, который действительно делает эти два полностью независимыми друг от друга.
Встроенные сценарии
Это действительно для крупномасштабных кодовых баз, но другой метод, который я считаю чрезвычайно полезным, - это использование встроенного языка сценариев, по крайней мере, для самых высокоуровневых частей вашей системы. Я обнаружил, что смог встраивать Lua за один день и иметь возможность одинаково вызывать все команды в нашей системе (к счастью, команды были абстрактными). К сожалению, я наткнулся на контрольно-пропускной пункт, где разработчики не доверяли внедрению другого языка и, возможно, наиболее странно, с производительностью как их наибольшим подозрением. Тем не менее, хотя я мог бы понять и другие проблемы, производительность не должна быть проблемой, если мы используем сценарий только для вызова команд, когда пользователи нажимают кнопки, например, которые не выполняют своих собственных значительных циклов (что мы пытаемся сделать, беспокоиться о разнице наносекунд во времени отклика на нажатие кнопки?).
пример
Между тем, наиболее эффективным способом, который я когда-либо видел после исчерпания методов сокращения времени компиляции в больших кодовых базах, являются архитектуры, которые действительно уменьшают объем информации, необходимой для работы какой-либо одной вещи в системе, а не просто отсоединяют один заголовок от другого от компилятора. в перспективе, но требуя, чтобы пользователи этих интерфейсов делали то, что им нужно делать, зная (как с точки зрения человека, так и с точки зрения компилятора, истинная развязка, выходящая за пределы зависимостей компилятора), как минимум.
ECS - это всего лишь один пример (и я не предлагаю вам его использовать), но столкнувшись с ним, я понял, что у вас могут быть действительно эпичные кодовые базы, которые все еще создаются на удивление быстро, при этом успешно используя шаблоны и множество других полезностей, потому что ECS, благодаря природа создает очень несвязную архитектуру, в которой системам нужно знать только о базе данных ECS и, как правило, лишь несколько типов компонентов (иногда только один), чтобы выполнять свою задачу:
Дизайн, Дизайн, Дизайн
И такого рода разъединенные архитектурные проекты на человеческом, концептуальном уровне более эффективны с точки зрения минимизации времени компиляции, чем любой из методов, которые я исследовал выше, по мере того, как ваша кодовая база растет, растет и растет, потому что этот рост не переводится на ваш средний уровень. модуль компиляции, умножающий количество информации, необходимой при компиляции, и время компоновки для работы (любая система, которая требует, чтобы ваш средний разработчик включил множество вещей, чтобы сделать что-либо, также требует их, а не только компилятор должен знать о большом количестве информации, чтобы сделать что-нибудь ). Он также имеет больше преимуществ, чем сокращение времени сборки и распутывание заголовков, поскольку это также означает, что разработчикам не нужно много знать о системе, помимо того, что требуется немедленно, чтобы что-то с ней сделать.
Если, например, вы можете нанять опытного разработчика физики для разработки физического движка для вашей игры AAA, который охватывает миллионы LOC, и он может начать работу очень быстро, зная абсолютную минимальную информацию о таких вещах, как типы и доступные интерфейсы а также ваши системные понятия, то это, естественно, приведет к уменьшению объема информации, необходимой и ему, и компилятору для создания его физического движка, и аналогичным образом приведет к значительному сокращению времени сборки, в то время как обычно подразумевается, что нет ничего похожего на спагетти. где-нибудь в системе. И вот что я предлагаю расставить приоритеты над всеми этими другими методами: как вы проектируете свои системы. Исчерпание других техник будет обледенением, если вы сделаете это в то время, в противном случае,