web-dev-qa-db-fra.com

Que faire d'un fichier source C ++ de 11 000 lignes?

Nous avons donc cet énorme fichier source (11000 lignes énorme?) Mainmodule.cpp dans notre projet et chaque fois que je dois le toucher, je grince des dents.

Comme ce fichier est si central et volumineux, il continue d'accumuler de plus en plus de code et je ne peux pas penser à un bon moyen de le faire commencer à rétrécir.

Le fichier est utilisé et modifié activement dans plusieurs (> 10) versions de maintenance de notre produit et il est donc très difficile de le refactoriser. Si je devais "simplement" le diviser, disons pour commencer, en 3 fichiers, la fusion des modifications des versions de maintenance deviendrait un cauchemar. Et aussi, si vous divisez un fichier avec un historique aussi long et riche, le suivi et la vérification des anciennes modifications de l'historique SCC deviennent soudainement beaucoup plus difficiles.

Le fichier contient essentiellement la "classe principale" (répartition et coordination du travail interne principal) de notre programme, donc chaque fois qu'une fonctionnalité est ajoutée, elle affecte également ce fichier et chaque fois qu'elle se développe. :-(

Que feriez-vous dans cette situation? Avez-vous des idées sur la façon de déplacer de nouvelles fonctionnalités vers un fichier source séparé sans gâcher le flux de travail SCC?

(Remarque sur les outils: nous utilisons C++ avec Visual Studio; Nous utilisons AccuRev comme SCC mais je pense que le type de SCC n'a pas vraiment d'importance ici; Nous utilisons Araxis Merge pour comparer et fusionner des fichiers)

228
Martin Ba
  1. Trouvez du code dans le fichier qui est relativement stable (ne change pas rapidement et ne varie pas beaucoup entre les branches) et pourrait être une unité indépendante. Déplacez-le dans son propre fichier, et d'ailleurs dans sa propre classe, dans toutes les branches. Parce qu'il est stable, cela ne provoquera pas (beaucoup) de fusions "maladroites" qui doivent être appliquées à un fichier différent de celui sur lequel il a été créé à l'origine, lorsque vous fusionnez le changement d'une branche à une autre. Répéter.

  2. Trouvez du code dans le fichier qui ne s'applique essentiellement qu'à un petit nombre de branches et pourrait être autonome. Peu importe que cela change rapidement ou non, en raison du petit nombre de branches. Déplacez-le dans ses propres classes et fichiers. Répéter.

Donc, nous nous sommes débarrassés du code qui est le même partout et du code spécifique à certaines branches.

Cela vous laisse un noyau de code mal géré - il est nécessaire partout, mais il est différent dans chaque branche (et/ou il change constamment de sorte que certaines branches courent derrière d'autres), et pourtant c'est dans un seul fichier que vous êtes tentative infructueuse de fusionner entre les branches. Arrêter de faire ça. Branche le fichier en permanence, peut-être en le renommant dans chaque branche. Ce n'est plus "principal", c'est "principal pour la configuration X". OK, vous perdez donc la possibilité d'appliquer la même modification à plusieurs branches en fusionnant, mais c'est en tout cas le cœur du code où la fusion ne fonctionne pas très bien. Si vous devez malgré tout gérer manuellement les fusions pour gérer les conflits, il n'est pas inutile de les appliquer manuellement indépendamment sur chaque branche.

Je pense que vous avez tort de dire que le type de SCC n'a pas d'importance, parce que par exemple les capacités de fusion de git sont probablement meilleures que l'outil de fusion que vous utilisez. Donc, le problème principal, "la fusion est difficile" se produit à des moments différents pour différents SCC. Cependant, il est peu probable que vous puissiez changer les SCC, donc le problème n'est probablement pas pertinent.

85
Steve Jessop

La fusion ne sera pas un si grand cauchemar qu'il le sera lorsque vous obtiendrez 30000 fichiers LOC à l'avenir. Alors:

  1. Arrêtez d'ajouter plus de code à ce fichier.
  2. Sépare le.

Si vous ne pouvez pas simplement arrêter de coder pendant le processus de refactoring, vous pouvez laisser ce gros fichier tel quel pendant un certain temps au moins sans y ajouter plus de code: puisqu'il contient une "classe principale", vous pourriez hériter et conserver les classes héritées avec des fonctions surchargées dans plusieurs nouveaux petits fichiers bien conçus.

129

Il me semble que vous faites face à un certain nombre d'odeurs de code ici. Tout d'abord, la classe principale semble violer le principe ouvert/fermé . On dirait aussi qu'il gère trop de responsabilités . Pour cette raison, je suppose que le code est plus fragile qu'il ne devrait l'être.

Bien que je puisse comprendre vos préoccupations concernant la traçabilité après une refactorisation, je m'attends à ce que cette classe soit assez difficile à maintenir et à améliorer et que tout changement que vous apportez soit susceptible de provoquer des effets secondaires. Je suppose que le coût de ceux-ci l'emporte sur le coût de refactorisation de la classe.

Dans tous les cas, puisque les odeurs du code ne feront que s'aggraver avec le temps, au moins à un certain point, le coût de celles-ci l'emportera sur le coût de la refactorisation. D'après votre description, je suppose que vous avez dépassé le point de basculement.

La refactorisation doit être effectuée par petites étapes. Si possible, ajoutez des tests automatisés pour vérifier le comportement actuel avant de refactoriser quoi que ce soit. Sélectionnez ensuite de petites zones de fonctionnalités isolées et extrayez-les en tant que types afin de déléguer la responsabilité.

En tout cas, ça sonne comme un projet majeur, alors bonne chance :)

67
Brian Rasmussen

La seule solution que j'aie jamais imaginée pour de tels problèmes est la suivante. Le gain réel par la méthode décrite est la progressivité des évolutions. Pas de révolutions ici, sinon vous aurez très vite des ennuis.

Insérez une nouvelle classe cpp au-dessus de la classe principale d'origine. Pour l'instant, il redirigerait essentiellement tous les appels vers la classe principale actuelle, mais viserait à rendre l'API de cette nouvelle classe aussi claire et succincte que possible.

Une fois cela fait, vous avez la possibilité d'ajouter de nouvelles fonctionnalités dans de nouvelles classes.

Quant aux fonctionnalités existantes, vous devez les déplacer progressivement dans de nouvelles classes à mesure qu'elles deviennent suffisamment stables. Vous perdrez SCC aide pour ce morceau de code, mais il n'y a pas grand-chose à faire à ce sujet. Choisissez simplement le bon timing.

Je sais que ce n'est pas parfait, mais j'espère que cela peut aider, et le processus doit être adapté à vos besoins!

Informations supplémentaires

Notez que Git est un SCC qui peut suivre des morceaux de code d'un fichier à un autre. J'ai entendu de bonnes choses à ce sujet, donc cela pourrait vous aider pendant que vous déplacez progressivement votre travail.

Git est construit autour de la notion de blobs qui, si je comprends bien, représentent des morceaux de fichiers de code. Déplacez ces pièces dans différents fichiers et Git les trouvera, même si vous les modifiez. En dehors de la vidéo de Linus Torvalds mentionnée dans les commentaires ci-dessous, je n'ai pas pu trouver quelque chose de clair à ce sujet.

49
Benoît

Confucius dit: "la première étape pour sortir du trou est d'arrêter de creuser le trou."

30
fdasfasdfdas

Laissez-moi deviner: dix clients avec des fonctionnalités différentes et un directeur des ventes qui promeut la "personnalisation"? J'ai déjà travaillé sur des produits comme ça auparavant. Nous avons eu essentiellement le même problème.

Vous reconnaissez qu'avoir un énorme fichier est un problème, mais encore plus de problèmes sont dix versions que vous devez garder "à jour". C'est une maintenance multiple. SCC peut rendre cela plus facile, mais il ne peut pas le faire correctement.

Avant d'essayer de diviser le fichier en plusieurs parties, vous devez synchroniser les dix branches afin de pouvoir voir et façonner tout le code à la fois. Vous pouvez le faire une branche à la fois, en testant les deux branches par rapport au même fichier de code principal. Pour appliquer le comportement personnalisé, vous pouvez utiliser #ifdef et friends, mais il vaut mieux autant que possible d'utiliser if/else ordinaire contre des constantes définies. De cette façon, votre compilateur vérifiera tous les types et éliminera très probablement le code objet "mort" de toute façon. (Vous pouvez cependant désactiver l'avertissement concernant le code mort.)

Une fois qu'il n'y a qu'une seule version de ce fichier partagée implicitement par toutes les branches, il est alors plus facile de commencer les méthodes de refactoring traditionnelles.

Les #ifdefs sont principalement meilleurs pour les sections où le code affecté n'a de sens que dans le contexte d'autres personnalisations par branche. On peut affirmer que ceux-ci présentent également une opportunité pour le même schéma de fusion de branches, mais ne deviennent pas fous. Un projet colossal à la fois, s'il vous plaît.

À court terme, le fichier semble augmenter. C'est acceptable. Ce que vous faites, c'est rassembler des choses qui doivent être réunies. Ensuite, vous commencerez à voir des zones clairement identiques quelle que soit la version; ceux-ci peuvent être laissés seuls ou refactorisés à volonté. Les autres domaines seront clairement différents selon la version. Vous avez un certain nombre d'options dans ce cas. Une méthode consiste à déléguer les différences aux objets de stratégie par version. Une autre consiste à dériver les versions client d'une classe abstraite commune. Mais aucune de ces transformations n'est possible tant que vous disposez de dix "astuces" de développement dans différentes branches.

25
Ian

Je ne sais pas si cela résout votre problème, mais ce que je suppose que vous voulez faire, c'est migrer le contenu du fichier vers des fichiers plus petits indépendants les uns des autres (résumé). Ce que j'obtiens aussi, c'est que vous avez environ 10 versions différentes du logiciel flottant et que vous devez les prendre en charge sans gâcher les choses.

Tout d'abord, il y a juste non une manière simple et qui se résoudra en quelques minutes de brainstorming. Les fonctions liées dans votre fichier sont toutes vitales pour votre application, et simplement les couper et les migrer vers d'autres fichiers ne sauveront pas votre problème.

Je pense que vous n'avez que ces options:

  1. Ne migrez pas et restez avec ce que vous avez. Quittez éventuellement votre travail et commencez à travailler sur des logiciels sérieux avec une bonne conception en plus. La programmation extrême n'est pas toujours la meilleure solution si vous travaillez sur un projet à long terme avec suffisamment de fonds pour survivre à un crash ou deux.

  2. Élaborez une présentation de l'apparence de votre fichier une fois qu'il sera divisé. Créez les fichiers nécessaires et intégrez-les dans votre application. Renommez les fonctions ou surchargez-les pour prendre un paramètre supplémentaire (peut-être juste un simple booléen?). Une fois que vous devez travailler sur votre code, migrez les fonctions sur lesquelles vous devez travailler vers le nouveau fichier et mappez les appels de fonction des anciennes fonctions vers les nouvelles fonctions. Vous devriez toujours avoir votre fichier principal de cette façon, et être toujours en mesure de voir les modifications qui y ont été apportées, une fois qu'il s'agit d'une fonction spécifique, vous savez exactement quand il a été externalisé, etc.

  3. Essayez de convaincre vos collègues avec un bon gâteau que le flux de travail est surfait et que vous devez réécrire certaines parties de l'application afin de faire des affaires sérieuses.

22
Robin

Exactement, ce problème est traité dans l'un des chapitres du livre "Working Effectively with Legacy Code" ( http://www.Amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 =).

19
Patrick

Je pense que vous feriez mieux de créer un ensemble de classes command qui correspondent aux points API du mainmodule.cpp.

Une fois qu'elles sont en place, vous devrez refactoriser la base de code existante pour accéder à ces points API via les classes de commandes, une fois cela fait, vous êtes libre de refactoriser l'implémentation de chaque commande dans une nouvelle structure de classe.

Bien sûr, avec une seule classe de 11 KLOC, le code est probablement très couplé et fragile, mais la création de classes de commande individuelles aidera beaucoup plus que toute autre stratégie de proxy/façade.

Je n'envie pas la tâche, mais avec le temps, ce problème ne fera qu'empirer s'il n'est pas résolu.

Mise à jour

Je dirais que le modèle de commande est préférable à une façade.

Il est préférable de maintenir/organiser un grand nombre de classes de commandement différentes sur une façade (relativement) monolithique. Le mappage d'une seule façade sur un fichier 11 KLOC devra probablement être divisé en plusieurs groupes différents.

Pourquoi prendre la peine de comprendre ces groupes de façade? Avec le modèle de commande, vous pourrez regrouper et organiser ces petites classes de manière organique, vous aurez donc beaucoup plus de flexibilité.

Bien sûr, les deux options sont meilleures que le seul fichier 11 KLOC et croissant.

14
ocodo

Un conseil important: ne mélangez pas le refactoring et les corrections de bugs. Ce que vous voulez, c'est une version de votre programme qui soit identique à la version précédente, sauf que le code source est différent.

Une façon pourrait être de commencer à diviser la fonction/pièce la moins volumineuse dans son propre fichier, puis à l'inclure avec un en-tête (transformant ainsi main.cpp en une liste de #includes, qui sonne une odeur de code en soi * je ne suis pas un gourou C++ cependant), mais au moins il est maintenant divisé en fichiers).

Vous pouvez alors essayer de basculer toutes les versions de maintenance vers le "nouveau" main.cpp ou quelle que soit votre structure. Encore une fois: pas d'autres changements ou corrections de bugs, car le suivi de ceux-ci est déroutant.

Une autre chose: autant que vous souhaitiez faire un grand passage pour refactoriser le tout en une seule fois, vous pourriez mordre plus que vous ne pouvez mâcher. Peut-être choisissez-vous simplement une ou deux "parties", insérez-les dans toutes les versions, puis ajoutez un peu plus de valeur pour votre client (après tout, la refactorisation n'ajoute pas de valeur directe, c'est donc un coût qui doit être justifié), puis choisissez une autre une ou deux parties.

Évidemment, cela nécessite une certaine discipline dans l'équipe pour utiliser les fichiers divisés et non seulement ajouter de nouvelles choses à la main.cpp tout le temps, mais encore une fois, essayer de faire un refactor massive ne peut pas être le meilleur plan d'action.

13
Michael Stum

Rofl, cela me rappelle mon ancien travail. Il semble que, avant de rejoindre, tout était dans un énorme fichier (également C++). Ensuite, ils l'ont divisé (à des points complètement aléatoires en utilisant des inclusions) en environ trois (fichiers encore énormes). La qualité de ce logiciel était, comme on pouvait s'y attendre, horrible. Le projet a totalisé environ 40 000 LOC. (ne contenant presque aucun commentaire mais BEAUCOUP de code en double)

Finalement, j'ai fait une réécriture complète du projet. J'ai commencé par refaire la pire partie du projet à partir de zéro. J'avais bien sûr en tête une possible (petite) interface entre cette nouvelle pièce et les autres. J'ai ensuite inséré cette partie dans l'ancien projet. Je n'ai pas refactorisé l'ancien code pour créer l'interface nécessaire, mais je l'ai simplement remplacé. Ensuite, j'ai fait de petits pas à partir de là, en réécrivant l'ancien code.

Je dois dire que cela a pris environ six mois et qu'il n'y a pas eu de développement de l'ancienne base de code à côté des corrections de bogues pendant cette période.


modifier:

La taille est restée à environ 40k LOC, mais la nouvelle application contenait beaucoup plus de fonctionnalités et probablement moins de bogues dans sa version initiale que le logiciel vieux de 8 ans. L'une des raisons de la réécriture était également que nous avions besoin des nouvelles fonctionnalités et les introduire dans l'ancien code était presque impossible.

Le logiciel était destiné à un système embarqué, une imprimante d'étiquettes.

Un autre point que je dois ajouter est qu'en théorie le projet était en C++. Mais ce n'était pas du tout OO, ça aurait pu être C. La nouvelle version était orientée objet.

10
ziggystar

Eh bien, je comprends votre douleur :) J'ai aussi participé à quelques projets de ce type et ce n'est pas joli. Il n'y a pas de réponse simple à cela.

Une approche qui peut fonctionner pour vous est de commencer à ajouter des gardes de sécurité dans toutes les fonctions, c'est-à-dire vérifier les arguments, les pré/post-conditions dans les méthodes, puis éventuellement ajouter des tests unitaires afin de capturer la fonctionnalité actuelle des sources. Une fois que vous avez cela, vous êtes mieux équipé pour retailler le code car vous aurez des assertions et des erreurs qui vous alerteront si vous avez oublié quelque chose.

Parfois, bien qu'il y ait des moments où la refactorisation peut simplement apporter plus de douleur que d'avantages. Ensuite, il peut être préférable de simplement laisser le projet d'origine et dans un état de pseudo maintenance et de recommencer à zéro, puis d'ajouter progressivement la fonctionnalité de la bête.

8
Anders

OK, donc pour la plupart, la réécriture de l'API du code de production est une mauvaise idée pour commencer. Deux choses doivent arriver.

Premièrement, votre équipe doit réellement décider de geler le code sur la version de production actuelle de ce fichier.

Deuxièmement, vous devez prendre cette version de production et créer une branche qui gère les versions à l'aide de directives de prétraitement pour fractionner le gros fichier. Il est plus facile de fractionner la compilation à l'aide des directives du préprocesseur JUST (#ifdefs, #includes, #endifs) que de recoder l'API. C'est certainement plus facile pour vos SLA et votre support continu.

Ici, vous pouvez simplement supprimer les fonctions liées à un sous-système particulier de la classe et les placer dans un fichier, par exemple mainloop_foostuff.cpp, et l'inclure dans mainloop.cpp au bon emplacement.

OU

Un moyen plus long mais plus robuste serait de concevoir une structure de dépendances internes avec double indirection dans la façon dont les choses sont incluses. Cela vous permettra de diviser les choses tout en prenant soin des co-dépendances. Notez que cette approche nécessite un codage positionnel et doit donc être associée à des commentaires appropriés.

Cette approche comprendrait des composants qui sont utilisés en fonction de la variante que vous compilez.

La structure de base est que votre mainclass.cpp inclura un nouveau fichier appelé MainClassComponents.cpp après un bloc d'instructions comme celui-ci:

#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"

La structure principale du fichier MainClassComponents.cpp serait là pour déterminer les dépendances au sein des sous-composants comme ceci:

#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  */

Et maintenant, pour chaque composant, vous créez un fichier component_xx.cpp.

Bien sûr, j'utilise des chiffres, mais vous devriez utiliser quelque chose de plus logique en fonction de votre code.

L'utilisation du préprocesseur vous permet de diviser les choses sans avoir à vous soucier des changements d'API, ce qui est un cauchemar en production.

Une fois la production réglée, vous pouvez réellement travailler sur la refonte.

8
Elf King

Ce n'est pas une réponse au gros problème, mais une solution théorique à un élément spécifique de celui-ci:

  • Déterminez où vous souhaitez diviser le gros fichier en sous-fichiers. Mettez des commentaires dans un format spécial à chacun de ces points.

  • Écrivez un script assez trivial qui divisera le fichier en sous-fichiers à ces points. (Les commentaires spéciaux contiennent peut-être des noms de fichiers intégrés que le script peut utiliser comme instructions pour le diviser.) Il doit conserver les commentaires dans le cadre du fractionnement.

  • Exécutez le script. Supprimez le fichier d'origine.

  • Lorsque vous devez fusionner à partir d'une branche, recréez d'abord le gros fichier en concaténant les morceaux ensemble, effectuez la fusion, puis divisez-le à nouveau.

De plus, si vous souhaitez conserver l'historique des fichiers SCC, je m'attends à ce que la meilleure façon de le faire soit d'indiquer à votre système de contrôle de code source que les fichiers pièce individuels sont des copies de l'original. conserver l'historique des sections qui ont été conservées dans ce fichier, bien qu'il enregistre également que de grandes parties ont été "supprimées".

4
Brooks Moses

Une façon de le diviser sans trop de danger serait de jeter un regard historique sur tous les changements de ligne. Y a-t-il certaines fonctions plus stables que d'autres? Points chauds de changement si vous voulez.

Si une ligne n'a pas été modifiée depuis quelques années, vous pouvez probablement la déplacer vers un autre fichier sans trop de soucis. Je regarderais la source annotée avec la dernière révision qui a touché une ligne donnée et voir s'il y a des fonctions que vous pourriez retirer.

4
Paul Rubel

Vous ne devez pas vous préoccuper de réduire la taille du fichier, mais plutôt de réduire la taille de la classe. Cela revient à peu près au même, mais vous fait regarder le problème sous un angle différent (comme @Brian Rasmussen suggère , votre classe semble avoir de nombreuses responsabilités).

4
Björn Pollex

Ce que vous avez est un exemple classique d'un modèle de conception connu appelé le blob . Prenez le temps de lire l'article que je signale ici, et vous trouverez peut-être quelque chose d'utile. De plus, si ce projet est aussi grand qu'il en a l'air, vous devriez envisager une conception pour éviter de devenir un code que vous ne pouvez pas contrôler.

4
David Conde

Mes sympathies - dans mon travail précédent, j'ai rencontré une situation similaire avec un fichier qui était plusieurs fois plus volumineux que celui que vous avez à traiter. La solution était:

  1. Écrivez du code pour tester de manière exhaustive la fonction dans le programme en question. On dirait que vous ne l'avez pas déjà en main ...
  2. Identifiez du code qui peut être extrait dans une classe helper/utilities. Pas besoin d'être gros, juste quelque chose qui ne fait pas vraiment partie de votre classe "principale".
  3. Refactorisez le code identifié en 2. dans une classe distincte.
  4. Relancez vos tests pour vous assurer que rien ne s'est cassé.
  5. Lorsque vous avez le temps, passez à 2. et répétez au besoin pour rendre le code gérable.

Les classes que vous créez à l'étape 3. les itérations se développeront probablement pour absorber plus de code approprié à leur fonction nouvellement claire.

Je pourrais également ajouter:

0: acheter livre de Michael Feathers sur l'utilisation du code hérité

Malheureusement, ce type de travail est bien trop courant, mais mon expérience est qu'il est très utile de pouvoir rendre le code horrible qui fonctionne mais moins horrible tout en le maintenant.

3
Steve Townsend

Wow, ça sonne bien. Je pense qu'expliquer à votre patron que vous avez besoin de beaucoup de temps pour refaçonner la bête vaut la peine d'être essayé. S'il n'est pas d'accord, arrêter est une option.

Quoi qu'il en soit, ce que je suggère est essentiellement de jeter toute l'implémentation et de la regrouper en nouveaux modules, appelons ces "services globaux". Le "module principal" ne transmettrait qu'à ces services et TOUT nouveau code que vous écrivez les utilisera à la place du "module principal". Cela devrait être réalisable dans un délai raisonnable (car il s'agit principalement de copier-coller), vous ne cassez pas le code existant et vous pouvez le faire une version de maintenance à la fois. Et s'il vous reste encore du temps, vous pouvez le passer à remanier tous les anciens modules dépendants pour utiliser également les services globaux.

3
back2dos

J'ai trouvé que cette phrase était la partie la plus intéressante de votre message:

> Le fichier est utilisé et modifié activement dans plusieurs (> 10) versions de maintenance de notre produit et il est donc très difficile de le refactoriser

Tout d'abord, je vous recommande d'utiliser un système de contrôle de source pour développer ces versions de maintenance 10+ qui prennent en charge la ramification.

Deuxièmement, je créerais dix succursales (une pour chacune de vos versions de maintenance).

Je peux déjà te sentir grincer des dents! Mais soit votre contrôle de code source ne fonctionne pas pour votre situation en raison d'un manque de fonctionnalités, soit il n'est pas utilisé correctement.

Maintenant, pour la branche sur laquelle vous travaillez - refactorisez-la comme bon vous semble, sachant que vous ne bouleverserez pas les neuf autres branches de votre produit.

Je serais un peu inquiet que vous ayez tant de choses dans votre fonction main ().

Dans tous les projets que j'écris, j'utiliserais main () uniquement pour effectuer l'initialisation des objets de base - comme un objet de simulation ou d'application - ces classes sont là où le vrai travail devrait continuer.

Je voudrais également initialiser un objet de journalisation d'application dans main pour une utilisation globale dans tout le programme.

Enfin, dans le principal, j'ajoute également du code de détection de fuite dans les blocs de préprocesseur qui garantit qu'il n'est activé que dans les versions DEBUG. C'est tout ce que j'ajouterais à main (). Main () devrait être court!

Vous dites que

> Le fichier contient essentiellement la "classe principale" (répartition et coordination du travail interne principal) de notre programme

Il semble que ces deux tâches puissent être divisées en deux objets distincts - un coordinateur et un répartiteur de travail.

Lorsque vous les divisez, vous pouvez gâcher votre "flux de travail SCC", mais il semble que le respect strict de votre SCC cause des problèmes de maintenance logicielle. Laissez tomber, maintenant et ne regardez pas en arrière, car dès que vous le réparerez, vous commencerez à dormir facilement.

Si vous n'êtes pas en mesure de prendre la décision, combattez bec et ongles avec votre manager pour cela - votre application doit être refactorisée - et mal par le bruit! Ne prenez pas non pour réponse!

2
user206705
  1. Ne touchez plus jamais ce fichier et le code!
  2. Le traitement est comme quelque chose avec lequel vous êtes coincé. Commencez à écrire des adaptateurs pour les fonctionnalités qui y sont encodées.
  3. Écrivez un nouveau code dans différentes unités et ne parlez qu'aux adaptateurs qui encapsulent les fonctionnalités du monstre.
  4. ... si un seul des éléments ci-dessus n'est pas possible, quittez le travail et obtenez-en un nouveau.
2
paul_71

Un autre livre que vous pourriez trouver intéressant/utile est Refactoring .

2
Derek

Comme vous l'avez décrit, le problème principal est différent entre le pré-partage et le post-partage, la fusion des corrections de bugs, etc. Il ne faudra pas autant de temps pour coder en dur un script en Perl, Ruby, etc. pour extraire la plupart du bruit provenant de la pré-division différente contre une concaténation de la post-division. Faites ce qui est le plus simple en termes de gestion du bruit:

  • supprimer certaines lignes avant/pendant la concaténation (par exemple, inclure des gardes)
  • supprimer d'autres éléments de la sortie diff si nécessaire

Vous pouvez même faire en sorte que chaque fois qu'il y a un archivage, la concaténation s'exécute et vous avez quelque chose de prêt à faire la différence avec les versions à fichier unique.

2
Tony Delroy

Mes 0,05 centimes d'euro:

Reconcevoir l'ensemble du désordre, le diviser en sous-systèmes en tenant compte des exigences techniques et commerciales (= de nombreuses pistes de maintenance parallèle avec une base de code potentiellement différente pour chacune, il y a évidemment un besoin de haute modifiabilité, etc.).

Lors de la division en sous-systèmes, analysez les endroits qui ont le plus changé et séparez ceux des parties immuables. Cela devrait vous montrer les points chauds. Séparez les parties les plus changeantes de leurs propres modules (par exemple, dll) de manière à ce que l'API du module puisse être conservée intacte et que vous n'ayez pas besoin de casser BC tout le temps. De cette façon, vous pouvez déployer différentes versions du module pour différentes branches de maintenance, si nécessaire, tout en conservant le noyau inchangé.

La refonte devra probablement être un projet distinct, essayer de le faire sur une cible en mouvement ne fonctionnera pas.

Quant à l'historique du code source, mon avis: oubliez-le pour le nouveau code. Mais gardez l'historique quelque part afin de pouvoir le vérifier, si nécessaire. Je parie que vous n'en aurez pas besoin autant après le début.

Vous devez très probablement obtenir l'adhésion de la direction pour ce projet. Vous pouvez peut-être argumenter avec un temps de développement plus rapide, moins de bogues, un entretien plus facile et moins de chaos global. Quelque chose dans le sens de "permettre de manière proactive la pérennité et la viabilité de la maintenance de nos actifs logiciels critiques" :)

C'est ainsi que je commencerais à aborder le problème au moins.

2
Slinky

Commencez par y ajouter des commentaires. En référence à l'endroit où les fonctions sont appelées et si vous pouvez déplacer les choses. Cela peut faire bouger les choses. Vous devez vraiment évaluer le degré de fragilité du code. Ensuite, déplacez les éléments communs de fonctionnalité ensemble. Petits changements à la fois.

2
Jesper Smith

Quelque chose que je trouve utile de faire (et je le fais maintenant, mais pas à l'échelle à laquelle vous faites face), est d'extraire des méthodes en tant que classes (refactoring d'objet de méthode). Les méthodes qui diffèrent selon vos différentes versions deviendront des classes différentes qui peuvent être injectées dans une base commune pour fournir le comportement différent dont vous avez besoin.

2
Channing Walton

Envisagez des moyens de réécrire l'intégralité de l'application de manière plus judicieuse. Peut-être en réécrire une petite section comme un prototype pour voir si votre idée est réalisable.

Si vous avez identifié une solution viable, refactorisez l'application en conséquence.

Si toutes les tentatives pour produire une architecture plus rationnelle échouent, vous savez au moins que la solution consiste probablement à redéfinir les fonctionnalités du programme.

2
wallyk

Juste deviner, si ce code dessert 10 clients et contient des variantes de code, vous pouvez avoir beaucoup de clones de code avec des variantes pour des clients spécifiques

Je serais très tenté d'exécuter un détecteur de clone sur votre fichier de 11 000 lignes. (En fait, si vous me l'envoyez, je le ferai avec mon détecteur de clone compatible C++ [voir la biographie] et je vous enverrai la réponse).

Cela montrerait tous les clones, et comment ces clones variaient. Avec ces informations, il pourrait être assez facile de refactoriser le code.

1
Ira Baxter

Je pense que la façon la plus simple de suivre l'historique des sources lors du fractionnement d'un fichier serait quelque chose comme ceci:

  1. Faites des copies du code source d'origine en utilisant les commandes de copie préservant l'historique fournies par votre système SCM. Vous devrez probablement soumettre à ce stade, mais il n'est pas encore nécessaire de parler à votre système de construction des nouveaux fichiers, donc ça devrait aller.
  2. Supprimez le code de ces copies. Cela ne devrait pas briser l'historique des lignes que vous conservez.
1

Il s'agit d'une refactorisation difficile et intéressante.

Tout d'abord, séparez l'implémentation de l'interface. Transformez cet énorme fichier en un shell vide qui ne fait que transférer l'appel et les paramètres. De cette façon, vous pouvez créer un composant avec une responsabilité limitée sans qu'aucun appelant ne soit affecté (ils appellent toujours l'énorme fichier/module/classe).

Pour cela, vous devez également rechercher l'heure de création de votre nouveau composant potentiel. Selon la façon dont le constructeur est envoyé, cela peut être assez délicat avec l'empilement des paramètres jusqu'à ce que vous les ayez tous.

ensuite, vous pouvez rechercher les appelants et les faire appeler votre composant. Voilà la partie facile.

1
LBarret

"Le fichier contient essentiellement la" classe principale "(répartition et coordination du travail interne principal) de notre programme, donc chaque fois qu'une fonctionnalité est ajoutée, elle affecte également ce fichier et chaque fois qu'elle se développe."

Si ce gros SWITCH (que je pense qu'il existe) devient le principal problème de maintenance, vous pouvez le refactoriser pour utiliser le dictionnaire et le modèle de commande et supprimer toute la logique de commutation du code existant vers le chargeur, qui remplit cette carte, c'est-à-dire:

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

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

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

Je pense que ce que je ferais dans cette situation est un peu la balle et:

  1. Découvrez comment je voulais diviser le fichier (basé sur la version de développement actuelle)
  2. Mettez un verrou administratif sur le fichier ("Personne ne touche mainmodule.cpp après 17h vendredi !!!"
  3. Passez votre long week-end à appliquer ce changement aux versions de maintenance> 10 (de la plus ancienne à la plus récente), jusqu'à la version actuelle incluse.
  4. Supprimez mainmodule.cpp de toutes les versions prises en charge du logiciel. C'est un nouvel âge - il n'y a plus de mainmodule.cpp.
  5. Convaincre la direction que vous ne devez pas prendre en charge plus d'une version de maintenance du logiciel (au moins sans un gros contrat de support $$$). Si chacun de vos clients a sa propre version unique .... yeeeeeshhhh. J'ajouterais des directives de compilation plutôt que d'essayer de maintenir 10+ fourches.

Le suivi des anciennes modifications apportées au fichier est simplement résolu par votre premier commentaire d'enregistrement disant quelque chose comme "split from mainmodule.cpp". Si vous devez revenir à quelque chose de récent, la plupart des gens se souviendront du changement, si c'est dans 2 ans, le commentaire leur dira où chercher. Bien sûr, quelle sera la valeur de revenir en arrière de plus de 2 ans pour voir qui a changé le code et pourquoi?

1
BIBD

Corrigez-moi si j'ai mal compris la question.

Pourquoi ne pouvez-vous pas diviser la source en fonctions ou en classes (fichiers séparés .h/.cpp) et les inclure comme en-têtes? Il doit certainement y avoir une réutilisation de certaines fonctionnalités.

Ce serait un début.

0
Sii