ti-enxame.com

O que fazer com um arquivo de origem C ++ de 11000 linhas?

Portanto, temos esse enorme arquivo de origem mainmodule.cpp (são 11000 linhas?) Em nosso projeto e toda vez que preciso tocá-lo, encolho-me.

Como esse arquivo é tão central e grande, ele acumula cada vez mais código e não consigo pensar em uma boa maneira de fazê-lo começar a encolher.

O arquivo é usado e alterado ativamente em várias (> 10) versões de manutenção do nosso produto e, portanto, é realmente difícil refatorá-lo. Se eu fosse "simplesmente" dividi-lo, digamos, para começar, em 3 arquivos, então mesclar as alterações das versões de manutenção se tornará um pesadelo. Além disso, se você dividir um arquivo com um histórico tão longo e rico, rastrear e verificar alterações antigas no histórico SCC repentinamente se torna muito mais difícil.

O arquivo contém basicamente a "classe principal" (expedição e coordenação do trabalho interno principal) do nosso programa, portanto, toda vez que um recurso é adicionado, ele também afeta esse arquivo e toda vez que ele cresce. :-(

O que você faria nesta situação? Alguma idéia de como mover novos recursos para um arquivo de origem separado sem atrapalhar o fluxo de trabalho SCC?

(Observação sobre as ferramentas: usamos C++ com Visual Studio; usamos AccuRev como SCC, mas acho que o tipo de SCC não importa realmente aqui; usamos Araxis Merge para fazer comparação e mesclagem reais de arquivos)

228
Martin Ba
  1. Encontre algum código no arquivo que seja relativamente estável (não mude rapidamente e não varie muito entre as ramificações) e possa permanecer como uma unidade independente. Mova isso para seu próprio arquivo e, para esse assunto, para sua própria classe, em todas as ramificações. Por ser estável, isso não causará (muitas) mesclagens "estranhas" que precisam ser aplicadas a um arquivo diferente daquele em que foram originalmente criadas, quando você mescla a alteração de uma ramificação para outra. Repetir.

  2. Encontre algum código no arquivo que basicamente se aplique apenas a um pequeno número de ramificações e que possa ser autônomo. Não importa se está mudando rapidamente ou não, devido ao pequeno número de ramificações. Mova isso para suas próprias classes e arquivos. Repetir.

Então, nos livramos do código que é o mesmo em todos os lugares e do código específico para determinadas ramificações.

Isso deixa você com um núcleo de código mal gerenciado - é necessário em qualquer lugar, mas é diferente em todos os ramos (e/ou muda constantemente para que alguns ramos sejam executados atrás de outros), e, no entanto, é em um único arquivo que você está sem êxito, tentando mesclar entre os ramos. Pare de fazer isso. Ramifique o arquivo permanentemente, talvez renomeando-o em cada ramificação. Não é mais "principal", é "principal para a configuração X". OK, então você perde a capacidade de aplicar a mesma alteração a várias ramificações mesclando, mas esse é o núcleo do código em que a mesclagem não funciona muito bem. Se você tiver que gerenciar manualmente as mesclagens para lidar com conflitos, não há perda em aplicá-las manualmente independentemente em cada ramificação.

Eu acho que você está errado ao dizer que o tipo de SCC não importa, porque, por exemplo, as habilidades de mesclagem do git são provavelmente melhores do que a ferramenta de mesclagem que você está usando. Portanto, o problema principal, "a fusão é difícil" ocorre em momentos diferentes para diferentes SCCs. No entanto, é improvável que você consiga alterar os SCCs, portanto o problema é provavelmente irrelevante.

85
Steve Jessop

A fusão não será um pesadelo tão grande quanto será quando você obter 30000 arquivos LOC no futuro. Assim:

  1. Pare de adicionar mais código a esse arquivo.
  2. Divida.

Se você não pode simplesmente parar de codificar durante o processo de refatoração, você pode deixar esse arquivo grande como está por um tempo, pelo menos, sem adicionar mais código: como ele contém uma "classe principal", você pode herdar e mantenha as classes herdadas com funções sobrecarregadas em vários novos arquivos pequenos e bem projetados.

129
Kirill V. Lyadvinsky

Parece-me que você está enfrentando uma série de cheiros de código aqui. Antes de tudo, a classe principal parece violar o princípio aberto/fechado . Também parece que está lidando com muitas responsabilidades . Devido a isso, eu assumiria que o código é mais frágil do que precisa.

Embora eu possa entender suas preocupações com relação à rastreabilidade após uma refatoração, espero que essa classe seja bastante difícil de manter e aprimorar e que quaisquer alterações que você faça provavelmente causem efeitos colaterais. Eu suporia que o custo destes supera o custo de refatoração da classe.

De qualquer forma, como o cheiro do código só piora com o tempo, pelo menos em algum momento, o custo destes compensará o custo da refatoração. Pela sua descrição, eu assumiria que você passou do ponto de inflexão.

Refatorar isso deve ser feito em pequenas etapas. Se possível, adicione testes automatizados para verificar o comportamento atual antes de refatorar qualquer coisa. Em seguida, escolha pequenas áreas de funcionalidade isolada e extraia-as como tipos para delegar a responsabilidade.

Em qualquer caso, parece um grande projeto, então boa sorte :)

67
Brian Rasmussen

A única solução que eu já imaginei para esses problemas segue. O ganho real pelo método descrito é a progressividade das evoluções. Não há revoluções aqui, caso contrário, você estará com problemas muito rapidamente.

Insira uma nova classe cpp acima da classe principal original. Por enquanto, ele basicamente redirecionaria todas as chamadas para a classe principal atual, mas visa tornar a API dessa nova classe o mais clara e sucinta possível.

Feito isso, você tem a possibilidade de adicionar novas funcionalidades em novas classes.

Quanto às funcionalidades existentes, é necessário movê-las progressivamente para novas classes, à medida que elas se tornam estáveis ​​o suficiente. Você perderá SCC ajuda para este trecho de código, mas não há muito que possa ser feito sobre isso. Basta escolher o momento certo.

Sei que isso não é perfeito, mas espero que ajude, e o processo deve ser adaptado às suas necessidades!

Informações adicionais

Observe que o Git é um SCC que pode seguir trechos de código de um arquivo para outro. Ouvi coisas boas sobre isso, para que possa ajudar enquanto você move progressivamente o seu trabalho.

O Git é construído em torno da noção de blobs que, se bem entendi, representam partes de arquivos de código. Mova essas partes em arquivos diferentes e o Git as encontrará, mesmo se você as modificar. Além de o vídeo de Linus Torvalds mencionado nos comentários abaixo, não consegui encontrar algo claro sobre isso.

49
Benoît

Confúcio diz: "o primeiro passo para sair do buraco é parar de cavar o buraco".

30
fdasfasdfdas

Deixe-me adivinhar: dez clientes com conjuntos de recursos divergentes e um gerente de vendas que promove a "personalização"? Já trabalhei em produtos como esse antes. Tivemos essencialmente o mesmo problema.

Você reconhece que ter um arquivo enorme é um problema, mas ainda mais problemas são as dez versões que você precisa manter "atual". Isso é manutenção múltipla. SCC pode facilitar isso, mas não pode corrigi-lo.

Antes de tentar dividir o arquivo em partes, é necessário sincronizar os dez ramos novamente, para que você possa ver e modelar todo o código de uma só vez. Você pode fazer isso uma ramificação por vez, testando as duas ramificações no mesmo arquivo de código principal. Para impor o comportamento personalizado, você pode usar #ifdef e amigos, mas é melhor usar o if/else comum contra constantes definidas. Dessa forma, seu compilador verificará todos os tipos e provavelmente eliminará o código do objeto "morto" de qualquer maneira. No entanto, você pode desativar o aviso sobre código morto.

Uma vez que existe apenas uma versão desse arquivo compartilhada implicitamente por todas as ramificações, é bastante mais fácil iniciar os métodos tradicionais de refatoração.

Os #ifdefs são principalmente melhores para seções em que o código afetado só faz sentido no contexto de outras personalizações por ramificação. Pode-se argumentar que estes também apresentam uma oportunidade para o mesmo esquema de fusão de agências, mas não se torne uma loucura. Um projeto colossal de cada vez, por favor.

No curto prazo, o arquivo parecerá aumentar. Isto está bom. O que você está fazendo é reunir coisas que precisam estar juntas. Posteriormente, você começará a ver áreas claramente iguais, independentemente da versão; estes podem ser deixados sozinhos ou refatorados à vontade. Outras áreas diferem claramente dependendo da versão. Você tem várias opções neste caso. Um método é delegar as diferenças em objetos de estratégia por versão. Outra é derivar versões de clientes de uma classe abstrata comum. Mas nenhuma dessas transformações é possível desde que você tenha dez "dicas" de desenvolvimento em diferentes ramos.

25
Ian

Não sei se isso resolve o seu problema, mas o que acho que você deseja fazer é migrar o conteúdo do arquivo para arquivos menores, independentes um do outro (resumidos). O que eu também entendo é que você tem cerca de 10 versões diferentes do software flutuando e precisa suportá-las sem bagunçar as coisas.

Antes de tudo, existe apenas não maneira que isso é fácil e se resolverá em alguns minutos de brainstorming. As funções vinculadas ao seu arquivo são vitais para o seu aplicativo, e simplesmente cortá-las e migrá-las para outros arquivos não salvarão o seu problema.

Eu acho que você só tem estas opções:

  1. Não migre e fique com o que você tem. Possivelmente, saia do emprego e comece a trabalhar em software sério, com bom design, além disso. A programação extrema nem sempre é a melhor solução se você estiver trabalhando em um projeto de longo prazo com fundos suficientes para sobreviver a uma falha ou duas.

  2. Elabore um layout de como você gostaria que seu arquivo fosse, assim que ele for dividido. Crie os arquivos necessários e integre-os ao seu aplicativo. Renomeie as funções ou sobrecarregue-as para obter um parâmetro adicional (talvez apenas um booleano simples?). Depois de trabalhar no seu código, migre as funções necessárias para o novo arquivo e mapeie as chamadas de função das funções antigas para as novas. Você ainda deve ter seu arquivo principal dessa maneira e ainda conseguir ver as alterações feitas nele, uma vez que se trata de uma função específica, você sabe exatamente quando foi terceirizado e assim por diante.

  3. Tente convencer seus colegas de trabalho com um bom bolo de que o fluxo de trabalho está superestimado e que você precisa reescrever algumas partes do aplicativo para fazer negócios sérios.

22
Robin

Exatamente esse problema é tratado em um dos capítulos do livro "Trabalhando efetivamente com o código legado" ( http://www.Amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 =).

19
Patrick

Eu acho que seria melhor criar um conjunto de comando classes que mapeiam para os pontos de API do mainmodule.cpp.

Uma vez implementados, você precisará refatorar a base de código existente para acessar esses pontos da API por meio das classes de comando; assim que estiver pronto, você poderá refatorar a implementação de cada comando em uma nova estrutura de classes.

Obviamente, com uma única classe de 11 KLOC, o código provavelmente é altamente acoplado e quebradiço, mas a criação de classes de comando individuais ajudará muito mais do que qualquer outra estratégia de proxy/fachada.

Não invejo a tarefa, mas, com o passar do tempo, esse problema só piorará se não for resolvido.

Atualizar

Eu sugeriria que o padrão de comando é preferível a uma fachada.

É preferível manter/organizar muitas classes de comando diferentes em uma fachada (relativamente) monolítica. O mapeamento de uma única fachada em um arquivo de 11 KLOC provavelmente precisará ser dividido em alguns grupos diferentes.

Por que se preocupar em tentar descobrir esses grupos de fachada? Com o padrão Comando, você poderá agrupar e organizar essas pequenas classes organicamente, para ter muito mais flexibilidade.

Obviamente, ambas as opções são melhores que o arquivo único de 11 KLOC e crescente.

14
ocodo

Um conselho importante: não misture refatoração e correções. O que você deseja é uma versão do seu programa que seja idêntica para a versão anterior, exceto que o código fonte é diferente.

Uma maneira poderia ser começar a dividir a menor função/parte em seu próprio arquivo e incluir um cabeçalho (transformando main.cpp em uma lista de #includes, que soa como um cheiro de código em si * Eu não sou um Guru de C++), mas pelo menos agora está dividido em arquivos).

Você pode tentar mudar todas as versões de manutenção para o "novo" main.cpp ou qualquer que seja sua estrutura. Novamente: Não há outras alterações ou correções de erros, porque rastrear essas coisas é confuso como o inferno.

Outra coisa: por mais que você deseje fazer um grande passe para refatorar a coisa toda de uma só vez, você pode morder mais do que pode mastigar. Talvez apenas escolha uma ou duas "partes", coloque-as em todos os lançamentos e adicione mais valor ao seu cliente (afinal, a refatoração não agrega valor direto, portanto, é um custo que deve ser justificado) e depois escolha outra uma ou duas partes.

Obviamente, isso requer alguma disciplina na equipe para realmente usar os arquivos divididos e não apenas adicionar coisas novas ao main.cpp o tempo todo, mas, novamente, tentar fazer um refator maciço pode não ser o melhor curso de ação.

13
Michael Stum

Rofl, isso me lembra meu antigo emprego. Parece que, antes de entrar, tudo estava dentro de um arquivo enorme (também em C++). Depois, eles o dividiram (em pontos completamente aleatórios usando inclusões) em cerca de três (arquivos ainda enormes). A qualidade deste software foi, como você poderia esperar, horrível. O projeto totalizou cerca de 40k LOC. (contendo quase nenhum comentário, mas MUITO código duplicado)

No final, fiz uma reescrita completa do projeto. Comecei refazendo a pior parte do projeto do zero. É claro que eu tinha em mente uma possível (pequena) interface entre essa nova parte e o resto. Então eu inseri essa parte no projeto antigo. Não refatorei o código antigo para criar a interface necessária, apenas o substituí. Depois dei pequenos passos a partir daí, reescrevendo o código antigo.

Devo dizer que isso levou cerca de meio ano e não houve desenvolvimento da antiga base de código além das correções durante esse período.


editar:

O tamanho ficou em cerca de 40k de LOC, mas o novo aplicativo continha muito mais recursos e presumivelmente menos bugs em sua versão inicial do que o software de 8 anos. Um dos motivos da reescrita foi também que precisávamos dos novos recursos e a introdução deles no código antigo era quase impossível.

O software era para um sistema incorporado, uma impressora de etiquetas.

Outro ponto que devo acrescentar é que, em teoria, o projeto era C++. Mas não era OO, poderia ter sido C. A nova versão era orientada a objetos.

10
ziggystar

Bem, eu entendo sua dor :) Eu já participei de alguns desses projetos e não é bonito. Não há uma resposta fácil para isso.

Uma abordagem que pode funcionar para você é começar a adicionar proteções seguras em todas as funções, ou seja, verificar argumentos, condições pré/pós-métodos nos métodos e, eventualmente, adicionar testes de unidade para capturar a funcionalidade atual das fontes. Depois de ter isso, você estará melhor equipado para re-fatorar o código, pois haverá declarações e erros aparecendo alertando você se você esqueceu alguma coisa.

Às vezes, embora haja momentos em que a refatoração pode trazer mais dor do que benefício. Em seguida, pode ser melhor deixar o projeto original e em um estado de pseudo manutenção e começar do zero e, em seguida, adicionar gradualmente a funcionalidade da besta.

8
Anders

OK, então, na maior parte das vezes, reescrever a API do código de produção é uma má ideia para começar. Duas coisas precisam acontecer.

Primeiro, você precisa que sua equipe decida congelar o código na versão atual de produção desse arquivo.

Segundo, você precisa pegar esta versão de produção e criar uma ramificação que gerencia as construções usando diretivas de pré-processamento para dividir o arquivo grande. Dividir a compilação usando as diretrizes do pré-processador JUST (#ifdefs, #includes, #endifs) é mais fácil do que recodificar a API. É definitivamente mais fácil para seus SLAs e suporte contínuo.

Aqui você pode simplesmente cortar funções relacionadas a um subsistema específico da classe e colocá-las em um arquivo, como mainloop_foostuff.cpp, e incluí-lo no mainloop.cpp no ​​local correto.

OU

Uma maneira mais demorada, porém robusta, seria criar uma estrutura de dependências internas com duplo indireção na maneira como as coisas são incluídas. Isso permitirá que você divida as coisas e ainda cuide das co-dependências. Observe que essa abordagem requer codificação posicional e, portanto, deve ser associada aos comentários apropriados.

Essa abordagem incluiria componentes usados ​​com base em qual variante você está compilando.

A estrutura básica é que seu mainclass.cpp incluirá um novo arquivo chamado MainClassComponents.cpp após um bloco de instruções como o seguinte:

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#Elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

A estrutura principal do arquivo MainClassComponents.cpp estaria lá para calcular dependências nos subcomponentes como este:

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

E agora, para cada componente, você cria um arquivo component_xx.cpp.

Claro que estou usando números, mas você deve usar algo mais lógico com base no seu código.

O uso do pré-processador permite dividir as coisas sem ter que se preocupar com alterações na API, o que é um pesadelo na produção.

Depois que a produção é liquidada, você pode realmente trabalhar no reprojeto.

8
Elf King

Esta não é uma resposta para o grande problema, mas uma solução teórica para uma parte específica:

  • Descubra onde você deseja dividir o arquivo grande em subarquivos. Coloque comentários em algum formato especial em cada um desses pontos.

  • Escreva um script bastante trivial que divida o arquivo em subarquivos nesses pontos. (Talvez os comentários especiais tenham incorporado nomes de arquivos que o script possa usar como instruções para dividi-lo.) Ele deve preservar os comentários como parte da divisão.

  • Execute o script. Exclua o arquivo original.

  • Quando você precisar mesclar a partir de uma ramificação, primeiro recrie o arquivo grande concatenando as peças novamente, faça a mesclagem e depois divida novamente.

Além disso, se você deseja preservar o histórico do arquivo SCC, espero que a melhor maneira de fazer isso seja informar ao sistema de controle de origem que os arquivos individuais da peça são cópias do original. Em seguida, preservará o histórico das seções que foram mantidas nesse arquivo, embora, é claro, também registre que grandes partes foram "excluídas".

4
Brooks Moses

Uma maneira de dividi-lo sem muito perigo seria dar uma olhada histórica em todas as mudanças de linha. Existem certas funções que são mais estáveis ​​que outras? Pontos quentes de mudança, se quiser.

Se uma linha não for alterada em alguns anos, você provavelmente poderá movê-la para outro arquivo sem muita preocupação. Eu daria uma olhada na fonte anotada com a última revisão que tocava uma determinada linha e veria se há alguma função que você possa executar.

4
Paul Rubel

Você não deve se preocupar em reduzir o tamanho do arquivo, mas em reduzir o tamanho da classe. Tudo se resume a quase o mesmo, mas faz você olhar o problema de um ângulo diferente (como @Brian Rasmussen sugere , sua classe parece ter muitas responsabilidades).

4
Björn Pollex

O que você tem é um exemplo clássico de um antipadrão de design conhecido chamado o blob . Reserve um tempo para ler o artigo que aponto aqui e talvez você encontre algo útil. Além disso, se esse projeto for do tamanho que parece, considere algum design para impedir o crescimento em código que você não pode controlar.

4
David Conde

Minhas simpatias - no meu trabalho anterior, encontrei uma situação semelhante com um arquivo que era várias vezes maior do que aquele com o qual você tem que lidar. A solução foi:

  1. Escreva um código para testar exaustivamente a função no programa em questão. Parece que você ainda não terá isso em mãos ...
  2. Identifique algum código que possa ser abstraído em uma classe auxiliar/utilitários. Não precisa ser grande, apenas algo que realmente não faz parte da sua classe 'principal'.
  3. Refatorar o código identificado em 2. em uma classe separada.
  4. Execute novamente seus testes para garantir que nada ocorra.
  5. Quando tiver tempo, vá para 2. e repita conforme necessário para tornar o código gerenciável.

As classes que você cria na etapa 3. provavelmente aumentam para absorver mais código apropriado à sua função recém-limpa.

Eu também poderia adicionar:

0: buy livro de Michael Feathers sobre como trabalhar com código legado

Infelizmente, esse tipo de trabalho é muito comum, mas minha experiência é de que existe um grande valor em tornar o código de trabalho, mas horrível, incrementalmente menos horrível, mantendo-o funcionando.

3
Steve Townsend

Uau, parece ótimo. Acho que explicar ao seu chefe que você precisa de muito tempo para refatorar a fera vale a pena tentar. Se ele não concordar, desistir é uma opção.

Enfim, o que eu sugiro é basicamente jogar toda a implementação e reagrupá-la em novos módulos, vamos chamar de "serviços globais". O "módulo principal" somente encaminharia para esses serviços e QUALQUER novo código que você escrever os usará em vez do "módulo principal". Isso deve ser possível em um período de tempo razoável (porque geralmente é copiar e colar), você não quebra o código existente e pode fazer uma versão de manutenção por vez. E se você ainda tiver algum tempo, poderá gastá-lo refatorando todos os módulos antigos dependentes para também usar os serviços globais.

3
back2dos

Achei esta frase a parte mais interessante do seu post:

> O arquivo é usado e alterado ativamente em várias (> 10) versões de manutenção do nosso produto e, portanto, é realmente difícil refatorá-lo

Primeiro, eu recomendaria que você usasse um sistema de controle de origem para desenvolver essas versões de manutenção com mais de 10 anos que suportam ramificação.

Segundo, eu criaria dez ramificações (uma para cada uma das suas versões de manutenção).

Já posso sentir você se encolhendo! Mas o controle de origem não está funcionando para a sua situação devido à falta de recursos ou não está sendo usado corretamente.

Agora, na filial em que você trabalha - refatore-o como achar melhor, seguro com o conhecimento de que você não perturbará os outros nove ramos do seu produto.

Eu ficaria um pouco preocupado que você tenha muito em sua função main ().

Em qualquer projeto que eu escreva, eu usaria main () apenas para executar a inicialização de objetos principais - como um objeto de simulação ou aplicativo - nessas classes é onde o trabalho real deve continuar.

Eu também inicializaria um objeto de log de aplicativo principal para uso global em todo o programa.

Finalmente, também adiciono o código de detecção de vazamento nos blocos de pré-processador, para garantir que ele seja ativado apenas nas compilações DEBUG. Isso é tudo que eu acrescentaria ao main (). Main () deve ser curto!

Você diz que

> O arquivo contém basicamente a "classe principal" (expedição e coordenação principal do trabalho interno) do nosso programa

Parece que essas duas tarefas podem ser divididas em dois objetos separados - um coordenador e um despachante.

Quando você os divide, você pode atrapalhar o seu "fluxo de trabalho SCC", mas parece que aderir estritamente ao seu fluxo de trabalho SCC está causando problemas de manutenção do software. Abandone-o agora e não olhe para trás, porque assim que você o consertar, você começará a dormir tranquilamente.

Se você não for capaz de tomar a decisão, lute com unhas e dentes com seu gerente - sua aplicação precisa ser refatorada - e muito pelo que parece! Não aceite não como resposta!

2
user206705
  1. Nunca toque neste arquivo e no código novamente!
  2. Tratar é como algo que você está preso. Comece a escrever adaptadores para a funcionalidade codificada lá.
  3. Escreva um novo código em unidades diferentes e fale apenas com adaptadores que encapsulam a funcionalidade do monstro.
  4. ... se apenas uma das opções acima não for possível, saia do emprego e obtenha um novo.
2
paul_71

Outro livro que você pode achar interessante/útil é Refatoração .

2
Derek

Como você descreveu, o principal problema é diferenciar pré-divisão versus pós-divisão, mesclando correções de bugs etc. Não demorará tanto tempo para codificar um script em Perl, Ruby etc. para eliminar a maior parte do ruído da pré-divisão diferente contra uma concatenação da pós-divisão. Faça o que for mais fácil em termos de manipulação de ruído:

  • remova determinadas linhas antes/durante a concatenação (por exemplo, incluir proteções)
  • remova outras coisas da saída diff, se necessário

Você pode fazer isso sempre que houver um check-in, a concatenação é executada e você tem algo preparado para diferir das versões de arquivo único.

2
Tony Delroy

Meus 0,05 eurocents:

Redesenhe toda a bagunça, divida-a em subsistemas, levando em consideração os requisitos técnicos e de negócios (= muitas trilhas de manutenção paralelas com base de código potencialmente diferente para cada uma; obviamente há uma necessidade de alta modificabilidade, etc.).

Ao dividir em subsistemas, analise os locais que mais mudaram e separe-os das partes imutáveis. Isso deve mostrar os pontos problemáticos. Separe as partes mais alteradas para seus próprios módulos (por exemplo, dll) de forma que a API do módulo possa ser mantida intacta e você não precisará interromper o BC o tempo todo. Dessa forma, você pode implantar versões diferentes do módulo para diferentes ramificações de manutenção, se necessário, mantendo o núcleo inalterado.

O redesenho provavelmente precisará ser um projeto separado, tentando fazê-lo em um alvo em movimento não funcionará.

Quanto ao histórico do código fonte, minha opinião: esqueça-o para o novo código. Mas mantenha a história em algum lugar para que você possa verificá-la, se necessário. Aposto que você não precisará muito disso desde o início.

Você provavelmente precisará obter a adesão da gerência para este projeto. Talvez você possa argumentar com um tempo de desenvolvimento mais rápido, menos erros, manutenção mais fácil e menos caos geral. Algo parecido com "Habilite proativamente a viabilidade de manutenção e futuro de nossos ativos críticos de software" :)

É assim que eu começaria a enfrentar o problema pelo menos.

2
Slinky

Comece adicionando comentários a ele. Com referência a onde as funções são chamadas e se você pode mover as coisas. Isso pode colocar as coisas em movimento. Você realmente precisa avaliar o quão frágil o código o baseia. Em seguida, junte bits comuns de funcionalidade. Pequenas mudanças de cada vez.

2
Jesper Smith

Algo que acho útil fazer (e estou fazendo agora, embora não na escala que você enfrenta), é extrair métodos como classes (refatoração de objeto de método). Os métodos que diferem nas diferentes versões se tornarão classes diferentes que podem ser injetadas em uma base comum para fornecer o comportamento diferente que você precisa.

2
Channing Walton

Considere maneiras de reescrever o aplicativo inteiro de uma maneira mais sensata. Talvez reescreva uma pequena seção como um protótipo para ver se sua ideia é viável.

Se você identificou uma solução viável, refatorar o aplicativo adequadamente.

Se todas as tentativas de produzir uma arquitetura mais racional falharem, pelo menos você sabe que a solução provavelmente está redefinindo a funcionalidade do programa.

2
wallyk

Apenas supondo que, se esse código atende 10 clientes e contém variantes de código, você pode ter muitos clones de código com variantes para clientes específicos

Eu ficaria muito tentado a executar um detector de clones no seu arquivo de 11.000 linhas. (De fato, se você enviar para mim, farei isso com o meu detector de clones com capacidade para C++ [veja a biografia] e enviarei a resposta).

Isso mostraria quaisquer clones, e como esses clones variavam. Com essas informações, pode ser bastante fácil refatorar o código.

1
Ira Baxter

Eu acho que a maneira mais fácil de acompanhar o histórico da fonte ao dividir um arquivo seria algo como isto:

  1. Faça cópias do código-fonte original, usando os comandos de cópia que preservam o histórico que seu sistema SCM fornece. Você provavelmente precisará enviar a essa altura, mas ainda não há necessidade de informar o sistema de compilação sobre os novos arquivos, portanto tudo deve estar bem.
  2. Exclua o código dessas cópias. Isso não deve quebrar a história das linhas que você mantém.
1
Christopher Creutzig

Esta é uma refatoração difícil e interessante.

Primeiro, separe a implementação da interface. Transforme esse arquivo enorme em um Shell vazio, que apenas encaminha a chamada e os parâmetros. Dessa forma, você pode criar componentes com responsabilidade limitada sem que nenhum chamador seja afetado (eles ainda chamam o enorme arquivo/módulo/classe).

Para isso completamente, você também precisa procurar o tempo de criação do seu novo componente em potencial. Dependendo de como o construtor é enviado, isso pode ser bastante complicado com o empilhamento de parâmetros até você ter todos eles.

então, você pode procurar os chamadores e fazê-los chamar seu componente. Essa é a parte fácil.

1
LBarret

"O arquivo contém basicamente a" classe principal "(expedição e coordenação do trabalho interno principal) do nosso programa, portanto, toda vez que um recurso é adicionado, ele também afeta esse arquivo e toda vez que cresce."

Se esse grande SWITCH (que eu acho que existe) se tornar o principal problema de manutenção, você poderá refatorá-lo para usar o dicionário e o padrão de comando e remover toda a lógica do switch do código existente para o carregador, que preenche esse mapa, ou seja:

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();
1
Grozz

Eu acho que o que eu faria nessa situação é um pouco complicado e:

  1. Descobrir como eu queria dividir o arquivo (com base na versão atual de desenvolvimento)
  2. Coloque um bloqueio administrativo no arquivo ("Ninguém toca em mainmodule.cpp após as 17h de sexta-feira !!!"
  3. Passe o fim de semana prolongado aplicando essa alteração nas> 10 versões de manutenção (da mais antiga para a mais recente), incluindo a versão atual.
  4. Exclua o mainmodule.cpp de todas as versões suportadas do software. É uma nova era - não há mais mainmodule.cpp.
  5. Convença o gerenciamento de que você não deve oferecer suporte a mais de uma versão de manutenção do software (pelo menos sem um grande contrato de suporte $$$). Se cada um de seus clientes tiver sua própria versão exclusiva .... yeeeeeshhhh. Eu estaria adicionando diretivas do compilador em vez de tentar manter mais de 10 garfos.

O rastreamento de alterações antigas no arquivo é simplesmente resolvido pelo seu primeiro comentário no check-in, dizendo algo como "split from mainmodule.cpp". Se você precisar voltar a algo recente, a maioria das pessoas lembrará da mudança. Se daqui a dois anos, o comentário dirá a eles onde procurar. Obviamente, quão valioso será voltar mais de dois anos para ver quem mudou o código e por quê?

1
BIBD

Corrija-me se entendi errado a pergunta.

Por que você não pode dividir a fonte como funções ou classes (arquivos .h/.cpp separados) e incluí-los como cabeçalhos? Certamente deve haver reutilização de algumas funcionalidades.

Isso seria um começo.

0
Sii