Dissociação
Em última análise, trata-se de desacoplar para mim no final do dia, no nível de design mais fundamental, sem as nuances das características de nossos compiladores e vinculadores. Quero dizer, você pode fazer coisas como fazer com que cada cabeçalho defina apenas uma classe, use pimpls, encaminhe declarações para tipos que só precisam ser declarados, não definidos, talvez até use cabeçalhos que apenas contenham declarações avançadas (ex:) <iosfwd>
, um cabeçalho por arquivo de origem , organize o sistema de forma consistente com base no tipo de coisa que está sendo declarada / definida etc.
Técnicas para reduzir "dependências em tempo de compilação"
E algumas das técnicas podem ajudar bastante, mas você pode se cansar dessas práticas e ainda achar que o arquivo de origem médio em seu sistema precisa de um preâmbulo de duas páginas. #include
diretivas para fazer algo ligeiramente significativo com tempos de construção disparados, se você concentrar muito na redução de dependências em tempo de compilação no nível do cabeçalho sem reduzir dependências lógicas em seus designs de interface e, embora isso possa não ser considerado "cabeçalho de espaguete", eu ainda diria que isso se traduz em questões prejudiciais semelhantes à produtividade na prática. No final do dia, se suas unidades de compilação ainda exigirem um monte de informações visíveis para fazer qualquer coisa, isso se traduzirá em aumento do tempo de compilação e multiplicará os motivos pelos quais você deve voltar e precisar mudar as coisas enquanto cria desenvolvedores sentem que estão dando uma cabeçada no sistema, apenas tentando concluir a codificação diária. Isto'
Você pode, por exemplo, fazer com que cada subsistema forneça um arquivo e uma interface de cabeçalho muito abstratos. Mas se os subsistemas não forem dissociados um do outro, você obterá algo parecido com espaguete novamente com interfaces de subsistema, dependendo de outras interfaces de subsistema com um gráfico de dependência que parece uma bagunça para funcionar.
Encaminhar declarações para tipos externos
De todas as técnicas que eu exausto para tentar obter uma antiga base de código que levou duas horas para ser construída, enquanto os desenvolvedores às vezes esperavam 2 dias pela sua vez no CI em nossos servidores de construção (você quase pode imaginar essas máquinas de construção como animais exaustos de carga, tentando freneticamente para acompanhar e falhar enquanto os desenvolvedores pressionam suas alterações), o mais questionável para mim foi declarar tipos definidos em outros cabeçalhos. E consegui reduzir essa base de código para 40 minutos ou mais, depois de anos fazendo isso em pequenos passos incrementais, enquanto tentava reduzir o "espaguete de cabeçalho", a prática mais questionável em retrospectiva (como em me fazer perder de vista a natureza fundamental do enquanto o túnel visava interdependências de cabeçalho) era encaminhar declarando tipos definidos em outros cabeçalhos.
Se você imaginar um Foo.hpp
cabeçalho com algo como:
#include "Bar.hpp"
E ele usa apenas Bar
no cabeçalho uma maneira que requer declaração, não definição. então, pode parecer um acéfalo declarar class Bar;
para evitar tornar a definição de Bar
visível no cabeçalho. Exceto na prática, muitas vezes você encontrará a maioria das unidades de compilação que Foo.hpp
ainda precisam Bar
ser definidas de qualquer maneira, com o ônus adicional de ter que Bar.hpp
se incluir por cima delas Foo.hpp
, ou você se depara com outro cenário em que isso realmente ajuda. % de suas unidades de compilação podem funcionar sem incluir Bar.hpp
, exceto que isso levanta a questão mais fundamental do design (ou pelo menos eu acho que deveria nos dias de hoje) de por que eles precisam ver a declaração Bar
e por queFoo
ainda precisa se preocupar em saber se é irrelevante para a maioria dos casos de uso (por que sobrecarregar um design com dependências para outro quase nunca usado?).
Porque conceitualmente não temos realmente dissociado Foo
de Bar
. Acabamos de fazer com que o cabeçalho de Foo
não precise de tanta informação sobre o cabeçalho Bar
, e isso não é tão substancial quanto um design que genuinamente os torna completamente independentes um do outro.
Script incorporado
Isso é realmente para bases de código de maior escala, mas outra técnica que considero imensamente útil é usar uma linguagem de script incorporada para pelo menos as partes de mais alto nível do seu sistema. Eu descobri que era capaz de incorporar Lua em um dia e que ele era capaz de chamar todos os comandos em nosso sistema de maneira uniforme (os comandos eram abstratos, felizmente). Infelizmente, encontrei um obstáculo em que os desenvolvedores desconfiavam da introdução de outro idioma e, talvez o mais bizarro, com o desempenho como sua maior suspeita. No entanto, embora eu possa entender outras preocupações, o desempenho não deve ser um problema se estivermos apenas utilizando o script para chamar comandos quando os usuários clicarem em botões, por exemplo, que não executam loops pesados (o que estamos tentando fazer, se preocupe com diferenças de nanossegundos nos tempos de resposta com um clique no botão?).
Exemplo
Enquanto isso, a maneira mais eficaz que já testemunhei após técnicas exaustivas para reduzir o tempo de compilação em grandes bases de código são arquiteturas que reduzem genuinamente a quantidade de informações necessárias para que qualquer coisa no sistema funcione, não apenas separando um cabeçalho de outro de um compilador perspectiva, mas exigindo que os usuários dessas interfaces façam o que precisam fazer enquanto conhecem (do ponto de vista humano e do compilador, o verdadeiro desacoplamento que vai além das dependências do compilador) o mínimo necessário.
O ECS é apenas um exemplo (e não estou sugerindo que você use um), mas, ao encontrá-lo, mostrou-me que você pode ter algumas bases de código realmente épicas que ainda constroem surpreendentemente rapidamente enquanto utilizam modelos e muitas outras vantagens porque o ECS, por natureza, cria uma arquitetura muito dissociada, na qual os sistemas precisam apenas conhecer o banco de dados do ECS e, geralmente, apenas um punhado de tipos de componentes (às vezes apenas um) para fazer suas coisas:
Design, Design, Design
E esses tipos de projetos arquitetônicos dissociados em um nível humano e conceitual são mais eficazes em termos de minimização do tempo de compilação do que qualquer uma das técnicas que eu explorei acima à medida que a sua base de código cresce, cresce e cresce, porque esse crescimento não se traduz na média unidade de compilação que multiplica a quantidade de informações necessárias nos tempos de compilação e link para funcionar (qualquer sistema que exija que o desenvolvedor médio inclua um monte de coisas para fazer qualquer coisa também exige isso, e não apenas o compilador para saber sobre muitas informações para fazer qualquer coisa ) Ele também tem mais benefícios do que tempos de construção reduzidos e desembaraçar cabeçalhos, pois também significa que seus desenvolvedores não precisam saber muito sobre o sistema além do que é imediatamente necessário para fazer algo com ele.
Se, por exemplo, você pode contratar um desenvolvedor de física especializado para desenvolver um mecanismo de física para o seu jogo AAA, que abrange milhões de LOC, e ele pode começar muito rapidamente, conhecendo as informações mínimas absolutas, no que diz respeito a tipos e interfaces disponíveis assim como os conceitos do sistema, isso naturalmente se traduzirá em uma quantidade reduzida de informações para ele e o compilador exigirem a construção de seu mecanismo de física, e também se traduzirá em uma grande redução nos tempos de compilação, ao mesmo tempo em que geralmente implica que não há nada parecido com espaguete em qualquer lugar do sistema. E é isso que estou sugerindo para priorizar acima de todas essas outras técnicas: como você projeta seus sistemas. O esgotamento de outras técnicas estará no topo se você o fizer enquanto, caso contrário,