A melhor forma de mudança de modelo de bagunça para aulas de arquitetura limpo (C ++)?

StackOverflow https://stackoverflow.com/questions/324043

  •  11-07-2019
  •  | 
  •  

Pergunta

Assumindo uma biblioteca de modelos largish com cerca de 100 arquivos contendo cerca de 100 modelos com total mais de 200.000 linhas de código. Alguns dos modelos usar herança múltipla para fazer o uso da biblioteca em si bastante simples (ou seja, herdam alguns modelos de base e ter apenas à execução de determinadas regras de negócio).

Tudo o que existe (crescido ao longo de vários anos), "obras" e é usado para projetos.

No entanto, a compilação de projetos utilizando essa biblioteca consome uma quantidade crescente de tempo e leva algum tempo para localizar a origem para determinados erros. Fixação muitas vezes provoca efeitos colaterais inesperados ou é muito difícil, porque alguns modelos interdependentes precisam mudar. O teste é quase impossível devido à enorme quantidade de funções.

Agora, eu realmente gostaria de simplificar a arquitetura de usar menos modelos e classes menores e mais especializadas.

Existe alguma maneira comprovada para ir sobre essa tarefa? O que seria um bom lugar para começar?

Foi útil?

Solução

Eu não tenho certeza que eu vejo como / por modelos são o problema, e por classes não-templated simples seria uma melhoria. Não que apenas significar até mesmo mais classes, menos a segurança de tipos e assim maior potencial para bugs?

Eu posso entender simplificar a arquitetura, refatoração e remoção de dependências entre as várias classes e modelos, mas automaticamente assumindo que "menos templates fará a arquitetura melhor" é falho imo.

Eu diria que modelos potencialmente permitem que você construa uma arquitetura mais limpa muito mais do que você ia ficar sem eles. Simplesmente porque você pode fazer classes separadas totalmente independente. Sem modelos, classes de funções que põem em outra classe deve saber sobre a classe ou uma interface que herda, com antecedência. Com modelos, este acoplamento não é necessário.

A remoção modelos só levaria a mais dependências, não menos. O agregado tipo de segurança de modelos podem ser usados ??para detectar um monte de erros em tempo de compilação (Polvilhe o seu código liberalmente com static_assert do para este fim)

É claro que o tempo de compilação adicionado pode ser uma razão válida para evitar modelos em alguns casos, e se você só tem um monte de programadores Java, que estão acostumados a pensar em "tradicionais" termos OOP, modelos pode confundi-los , que pode ser outra razão válida para modelos a evitar.

Mas de um ponto de vista da arquitetura, acho evitando modelos é um passo na direção errada.

Refactor a aplicação, com certeza, parece que o que é necessário. Mas não jogue fora uma das ferramentas mais úteis para a produção de código extensível e robusta apenas porque a versão original do aplicativo mal utilizado ele. Especialmente se você já está preocupado com a quantidade de código, removendo modelos provavelmente levará a mais linhas de código.

Outras dicas

Você precisa de testes automatizados, de que maneira nos anos de tempo de dez quando o seu sucessor tem o mesmo problema que ele pode refatorar o código (provavelmente adicionar mais modelos, porque ele acha que vai simplificar o uso da biblioteca) e sei que ainda atende a todas teste casos. Da mesma forma os efeitos colaterais de quaisquer pequenas correções de bugs serão imediatamente visíveis (assumindo que seus casos de teste são bons).

Além disso, "dividir e conqueor"

testes Write unidade.

Quando o novo código deve fazer o mesmo que o código antigo.

Essa é uma dica, pelo menos.

Editar:

Se você depreciar código antigo que tenha sido substituída com a nova funcionalidade pode fase para o novo código pequeno a pouco.

Bem, o problema é que maneira modelo de pensar é muito diferente da maneira baseada em herança orientada a objetos. É difícil responder a qualquer outra coisa do que "redesenhar a coisa toda e começar do zero".

É claro, pode haver uma maneira simples para um caso particular. Nós não podemos dizer sem saber mais sobre o que você tem.

O facto da solução de modelo é tão difícil manter é uma indicação de uma má concepção de qualquer maneira.

Alguns pontos (mas nota: estes são não o mal, de fato Se você quer mudar para código não-modelo, no entanto, isso pode ajudar.):


Lookup suas interfaces estáticas . Onde modelos depender do que existem funções? Onde é que eles precisam typedefs?

Coloque as partes comuns em uma classe base abstrata. Um bom exemplo é quando acontecer de você tropeçar o idioma CRTP. Você pode simplesmente substituí-lo com uma classe base abstrata que tem funções virtuais.

Lookup inteiro listas . Se você encontrar o seu código usa listas integrais como list<1, 3, 3, 1, 3>, você pode substituí-los com std::vector, se todos os códigos de usá-los pode viver com o trabalho com valores de tempo de execução em vez de expressões constantes.

tipo Lookup traços . Há muito código envolvido verificar se existe algum typedef, ou se algum método existe no código de modelo típico. Abstratos baseclasses resolver esses dois problemas usando métodos virtuais puros, e herdando typedefs para a base. Muitas vezes, typedefs só são necessários para acionar recursos hediondos como SFINAE , que, então, seria supérfluo também.

expressão Lookup modelos . Se o seu código utiliza modelos de expressão para evitar a criação de temporários, você terá que eliminá-los e usar o modo tradicional de retornar / passando temporários para os operadores envolvidos.

Função Lookup objetos . Se você encontrar seus usos código de função objetos, você pode alterá-los para usar classes base abstratas também, e ter algo como void run(); de chamá-los (ou se você quiser continuar usando operator(), melhor assim! Ele pode ser virtual também).

Como eu entendo, você está mais preocupado com o tempo de construção e a manutenção da sua biblioteca?

Primeiro, não tente "consertar" tudo de uma vez.

Em segundo lugar, entender o que você corrigir. complexidade do modelo é lá muitas vezes por uma razão, por exemplo, para impor certo uso, e fazer o compilador ajuda você não cometer um erro. Essa razão pode às vezes ser levado para longe, mas jogando fora 100 linhas, porque "ninguém sabe realmente o que eles fazem" não deve ser tomada de ânimo leve. Tudo o que eu sugiro aqui pode introduzir erros realmente desagradáveis, você foi avisado.

Em terceiro lugar, considero correções mais baratas em primeiro lugar: por exemplo, máquinas mais rápidas ou ferramentas de compilação distribuídos. Pelo menos, jogar em toda a RAM as placas vai demorar, e jogar fora os discos antigos. Ele faz maike a diferença. Uma unidade para OS, uma unidade de construção é um barato mans RAID.

é a biblioteca bem documentado? Essa é a sua melhor chance de torná-lo olhar para ferramentas como doxygen que ajudam a criar um tal documentação.

Todos considerado? OK, agora algumas sugestões para os tempos de construção;)


Entenda o C ++ modelo de construção : cada .cpp é compilado individualmente. Isso significa que muitos arquivos .cpp com muitos cabeçalhos = enorme construção. Este não é um conselho para colocar tudo em um arquivo .cpp, embora! No entanto, um truque (!) Que pode acelerar uma compilação imensamente é criar um único arquivo .cpp que inclui um monte de arquivos .cpp, e só se alimentam esse arquivo "mestre" para o compilador. Você não pode fazer isso cegamente, embora -. Você precisa entender os tipos de erros que este poderia introduzir

Se você não tiver um ainda, obter uma máquina de compilação separada, que você pode remoto em. Você vai ter que fazer um monte de quase-completo constrói para verificar se você quebrou alguns incluem. Você vai querer executar isso em outra máquina, que não impedi-lo de trabalhar em outra coisa. A longo prazo, você vai precisar dele para a integração compilações diárias de qualquer maneira;)

Use pré-compilado cabeçalhos . (Escalas melhor com máquinas rápidas, veja acima)

Verifique se o seu política de inclusão de cabeçalho . Enquanto cada arquivo deve ser "independente" (ou seja, incluem tudo o que precisa para ser incluído por outra pessoa), não inclua liberalmente. Infelizmente, eu ainda não encontrei uma ferramenta para encontrar declarações #incldue desnecessários, mas pode ajudar a gastar algum tempo de remover cabeçalhos não utilizados em arquivos "hotspot".

Criar e usar declarações para a frente para os modelos que você usa. Muitas vezes, você pode incldue um cabeçalho com as declarações forwad em muitos lugares, e usar o cabeçalho completo apenas em uns poucos específicos. Isso pode ajudar muito o tempo de compilação. Verifique o cabeçalho <iosfwd> como a biblioteca padrão faz isso para i / o riachos.

sobrecargas para modelos para alguns tipos : Se você tem um modelo de função complexa que é útil somente para alguns poucos tipos como este:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

Você pode declarar as sobrecargas no cabeçalho, e mover o modelo para o corpo:

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

Isso move o modelo longo para uma única unidade de compilação.
Infelizmente, esta é apenas de uso limitado para as aulas.

Verifique se o Pimpl idioma lata mover o código dos cabeçalhos em arquivos .cpp.

A regra geral que se esconde por trás que é separar a interface da sua biblioteca a partir da implementação . Use comentários, namesapces detail e cabeçalhos .impl.h separadas para mentalmente e fisicamente isolar o que deve ser conhecido para o exterior a partir de como ele é realizado. Isso expõe o valor real de sua biblioteca (faz isso complexidade realmente encapsular?), E dá-lhe a oportunidade de substituir "alvos fáceis" primeiro.


aconselhar mais específico - e quão útil o dado é - depende, em grande parte sobre a biblioteca real.

Boa sorte!

Como mencionado, os testes de unidade são uma boa idéia. Na verdade, ao invés de quebrar seu código através da introdução de alterações "simples" que são susceptíveis de ondulação fora, apenas concentrar na criação de um conjunto de testes, e corrigir não-conformidade com os ensaios. Possui uma atividade para atualizar os testes quando erros vir à luz.

Além disso, gostaria de sugerir atualizar suas ferramentas, se possível, para ajudar com problemas de depuração do modelo-relacionado.

Eu sempre se deparar com modelos antigos que eram enorme e exigiu uma série de tempo e memória para instanciar, mas não precisa ser. Nesses casos, a maneira mais fácil de cortar a gordura era tomar todo o código que não dependem de qualquer um dos argumentos de modelo e escondê-lo em funções separadas definidas em uma unidade de tradução normal. Isto também teve o efeito colateral positivo de disparo menos recompilações quando este código teve de ser ligeiramente modificado ou alterado documentação. Parece bastante óbvio, mas é realmente surpreendente como muitas vezes as pessoas escrevem um modelo de classe e acho que tudo o que faz tem que ser definido no cabeçalho, em vez de apenas o código que necessita da informação templated.

Outra coisa que você pode querer considerar é quantas vezes você limpar as hierarquias de herança, tornando os modelos de estilo "mixin" em vez de agregações de herança múltipla. Veja quantos lugares que você pode ir longe com tornando um dos argumentos de modelo o nome da classe base que deve derivar de (as obras maneira boost::enable_shared_from_this). Claro que isso normalmente só funciona bem se os construtores não têm argumentos, que você não precisa se preocupar com inicializar nada corretamente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top