Que faire à propos d'un fichier source C++ 11000 lignes?

Nous avons donc cet énorme (est-ce que 11000 lignes sont énormes?) mainmodule.fichier source cpp dans notre projet et chaque fois que je dois le toucher, je grincer 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 réellement commencer à rétrécir.

Le fichier est utilisé et modifié activement dans plusieurs (>10) versions de maintenance de notre produit et il est donc vraiment difficile de le refactoriser. Si je "simplement" les diviser, disons pour commencer, en 3 fichiers, puis la fusion des modifications des versions de maintenance deviendra un cauchemar. Et aussi si vous divisez un fichier avec une histoire aussi longue et riche, le suivi et la vérification des anciens changements dans l'historique SCC devient soudainement beaucoup plus difficile.

Le fichier contient essentiellement la "main class" (main internal work dispatching and coordination) de notre programme, donc chaque fois qu'une fonctionnalité est ajoutée, elle affecte également ce fichier et chaque fois qu'il se développe. :-(

, Que feriez-vous dans cette situation? 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?

(Note 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 faire de comparaison et fusion de fichiers)

222
demandé sur Martin Ba 2010-09-01 11:16:43

30 réponses

  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. La déplacer dans son propre fichier, et d'ailleurs dans sa propre classe, dans toutes les branches. Parce qu'il est stable, cela ne causera pas (beaucoup) de fusions" gênantes " qui doivent être appliquées à un fichier différent de celui sur lequel elles ont été créées à l'origine, lorsque vous fusionnez le changement d'une branche à l'autre. Répéter.

  2. Trouvez-en code dans le fichier qui ne s'applique fondamentalement qu'à un petit nombre de branches, et pourrait rester seul. Peu importe que cela change rapidement ou non, en raison du petit nombre de branches. Déplacez ceci 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 qui est spécifique à certaines branches.

Cela vous laisse avec 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 essayez sans succès de fusionner entre les branches. Arrêter de faire ça. Branchez le fichier de façon permanente , peut-être en le renommant dans chaque branche. Ce n'est plus" principal", c'est"principal pour la configuration X". OK, donc vous perdez la possibilité d'appliquer le même changement à 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 gérer manuellement les fusions de toute façon pour gérer les conflits, alors il n'y a aucune perte à 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, car par exemple les capacités de fusion de git sont probablement meilleures que l'outil de fusion que vous utilisez. Ainsi, le problème de base," la fusion est difficile " se produit à des moments différents pour différents CC. Cependant, il est peu probable que vous puissiez changer de SCCs, donc le problème est probablement hors de propos.

83
répondu Steve Jessop 2010-09-01 11:08:50

La fusion ne sera pas un si grand cauchemar que ce sera quand vous obtiendrez 30000 fichier LOC dans le futur. Donc:

  1. arrêtez d'ajouter plus de code à ce fichier.
  2. partagez-le.

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

127
répondu Kirill V. Lyadvinsky 2010-09-04 09:39:07

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

Bien que je puisse comprendre vos préoccupations concernant la traçabilité suite à un refactoring, Je m'attendrais à ce que cette classe soit plutôt difficile à maintenir et à améliorer et que tout changement que vous faites faire sont susceptibles de causer des effets secondaires. Je suppose que le coût de ceux-ci l'emporte sur le coût de refactoring de la classe.

Dans tous les cas, puisque les odeurs de code ne feront qu'empirer avec le temps, au moins à un moment donné, leur coût l'emportera sur le coût du refactoring. D'après votre description, je suppose que vous avez dépassé le point de basculement.

Refactoring cela devrait être fait en petites étapes. Si possible, ajoutez des tests automatisés pour vérifier le comportement actuel avant refactoring quoi que ce soit. Ensuite, choisissez 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, cela ressemble à un projet majeur, alors bonne chance:)

66
répondu Brian Rasmussen 2010-09-01 07:37:59

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

Insérer 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 que cela a été fait, vous avez la possibilité d'ajouter nouvelles fonctionnalités dans les nouvelles classes.

En ce qui concerne les fonctionnalités existantes, vous devez les déplacer progressivement dans de nouvelles classes à mesure qu'elles deviennent suffisamment stables. Vous perdrez L'aide SCC pour ce morceau de code, mais il n'y a pas grand-chose qui peut être fait à ce sujet. Il suffit de choisir 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 Complémentaires

Notez que Git est un SCC qui peut suivre morceaux de code d'un fichier à l'autre. J'ai entendu de bonnes choses à ce sujet, donc cela pourrait 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 morceaux dans différents fichiers et Git les trouvera, même si vous les modifiez. Mis à part La vidéo de Linus Torvalds mentionnée dans les commentaires ci-dessous, je n'ai pas pu trouver quelque chose de clair à propos de ce.

49
répondu Benoît 2010-09-04 09:37:10

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

29
répondu fdasfasdfdas 2010-09-02 06:54:50

Laissez-moi deviner: dix clients avec des ensembles de fonctionnalités divergentes et un directeur des ventes qui favorise la "personnalisation"? J'ai déjà travaillé sur des produits comme ça. Nous avions essentiellement le même problème.

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

Avant d'essayer de diviser le fichier en parties, vous devez apporter les dix les branches sont synchronisées les unes avec les autres afin que vous puissiez 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 est préférable 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 probablement le code objet "mort" de toute façon. (Vous pouvez désactiver l'avertissement sur le code mort, bien.)

Une fois qu'il n'y a qu'une seule version de ce fichier partagée implicitement par toutes les branches, il est plutôt 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 soutenir que ceux-ci présentent également une opportunité pour le même schéma de fusion de branches, mais ne vont pas de porc-sauvage. Un projet colossal à un moment, s'il vous plaît.

À court terme, le fichier apparaîtra à croître. C'est OK. Ce que vous faites, c'est rassembler les choses qui doivent être ensemble. Ensuite, vous commencerez à voir des zones qui sont clairement les mêmes quelle que soit la version; ceux-ci peuvent être laissés seuls ou refactorisés à volonté. D'autres zones différeront clairement 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 des versions client à partir d'une classe abstraite commune. Mais aucun d' ces transformations sont possibles tant que vous avez dix "conseils" de développement dans différentes branches.

24
répondu Ian 2010-09-01 08:40:07

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

Tout d'abord, il n'y a juste Pas de façon dont cela est facile et se résoudra en quelques minutes de remue-méninges. Les fonctions liées dans votre fichier sont toutes vital pour votre application, et simplement les couper et les migrer vers d'autres fichiers ne sauvera pas votre problème.

Je pense que vous avez seulement ces options:

  1. Ne pas migrer et rester avec ce que vous avez. Peut-être quitter votre travail et commencer à travailler sur un logiciel 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 de longue date avec suffisamment de fonds pour survivre à un accident ou deux.

  2. Une mise en page de comment vous aimeriez que votre fichier ressemble une fois qu'il est 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 dans le nouveau fichier et mappez les appels de fonctions des anciennes fonctions aux nouvelles fonctions. Vous devriez toujours avoir votre fichier principal de cette façon, et être toujours capable 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é et ainsi de suite.

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

22
répondu Robin 2010-09-01 07:37:01

Exactement ce problème est traité dans L'un des chapitres du livre " travailler efficacement avec le code hérité "( http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

19
répondu Patrick 2010-09-01 07:35:46

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

Une fois qu'ils sont en place, vous devrez refactoriser la base de code existante pour accéder à ces points API via les classes de commande, 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 en créant les classes de commandes individuelles aideront 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 abordé.

Mettre à jour

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

Maintenir / organiser beaucoup de classes de commandes différentes sur une façade (relativement) monolithique est préférable. Mappage D'une seule façade sur un fichier 11 KLOC devra probablement être divisé en un quelques-uns des différents groupes d'elle-même.

Pourquoi essayer de comprendre ces groupes de façade? Avec le modèle de Commande, vous serez en mesure de regrouper et d'organiser ces petites classes organique, de sorte que vous avez beaucoup plus de flexibilité.

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

14
répondu Slomojo 2010-09-04 09:29:06

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 est identique à la version précédente, sauf que le code source est différent.

Une façon pourrait être de commencer à diviser la moins grande fonction / partie dans son propre fichier, puis soit inclure avec un en-tête (tournant ainsi principal.cpp dans 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 fichier).

Vous pouvez alors essayer de basculer toutes les versions de maintenance sur la" nouvelle " main.cpp ou quelle que soit votre structure. Encore une fois: pas d'autres changements ou corrections de bugs parce que le suivi de ceux-ci est déroutant comme l'enfer.

Autre chose: autant que vous pouvez désirer faire une grosse passe à refactoriser le tout en une seule fois, vous pourriez mordre plus que vous ne pouvez mâcher. Peut-être juste choisir une ou deux "pièces", les mettre dans toutes les versions, puis Ajouter un peu plus de valeur pour votre client (après tout, Le Refactoring n'ajoute pas de valeur directe, c'est donc un coût qui doit être justifié) et ensuite choisir une ou deux autres parties.

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

13
répondu Michael Stum 2010-09-01 09:38:36

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

À la fin j'ai fait une réécriture complète du projet. J'ai commencé par refaire la pire partie de l' projet à partir de zéro. Bien sûr, j'avais en tête une possible (petite) interface entre cette nouvelle partie et le reste. Ensuite, j'ai 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 juste remplacé. Puis j'ai fait de petits pas à partir de là, en réécrivant l'ancien code.

Je dois dire que cela a pris environ une demi-année et qu'il n'y a pas eu de développement de l'ancienne base de code à côté des corrections de bugs pendant ce temps.


Modifier:

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

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

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

10
répondu ziggystar 2010-09-01 12:37:38

OK donc pour la plupart réécrire L'API du code de production est une mauvaise idée comme début. Deux choses doivent se produire.

Premièrement, vous devez réellement demander à votre équipe de décider de faire un gel de 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 builds en utilisant des directives de prétraitement pour diviser le gros fichier. Diviser la compilation en utilisant uniquement les directives du préprocesseur (#ifdefs, #includes, # endifs) est plus facile que de recoder L'API. C'est certainement plus facile pour vos SLA et votre soutien continu.

Ici, vous pouvez simplement découper les fonctions qui se rapportent à un sous-système particulier dans la classe et les mettre dans un fichier par exemple mainloop_foostuff.cpp et l'inclure dans mainloop.au rpc et au bon endroit.

OU

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

Cette approche inclurait 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.rpc après un bloc d'instructions comme suit:

#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 primaire du MainClassComponents.le fichier cpp serait là pour travailler sur les dépendances dans les 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 component_xx.cpp fichier.

Bien sûr, j'utilise des nombres 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 que vous avez réglé la production, vous pouvez alors réellement travailler sur la refonte.

8
répondu Elf King 2010-09-01 09:11:43

Eh bien, je comprends votre douleur :) j'ai été dans quelques projets de ce type et ce n'est pas joli. Il n'y a pas de réponse facile à 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 re-factoriser le code car vous aurez des affirmations et des erreurs surgissant vous alerter si vous avez oublié quelque chose.

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

8
répondu claptrap 2010-09-04 09:40:07

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

4
répondu Björn Pollex 2017-05-23 11:54:25

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

4
répondu David Conde 2010-09-01 12:04:34

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

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

  • Écrivez un script assez trivial qui divisera le fichier en sous-fichiers à ces points. (Peut-être que les commentaires spéciaux ont des noms de fichiers intégrés que le script peut utiliser comme instructions pour le diviser.) Il devrait préserver les commentaires dans le cadre de la scission.

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

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

Aussi, si vous voulez préserver l'historique des fichiers SCC, Je m'attends à ce que la meilleure façon de le faire soit de dire à votre système de contrôle de source que les fichiers individuels sont des copies de l'original. Ensuite, il permettra de préserver l' historique des sections qui ont été conservés dans ce fichier, bien que bien sûr, il enregistrera également que de grandes parties ont été "supprimés".

4
répondu Brooks Moses 2010-09-02 07:07:53

Une façon de le diviser sans trop de danger serait de jeter un regard historique sur tous les changements de ligne. Certaines fonctions sont plus stables que d'autres? Des 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 voudrais jeter un oeil à 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
répondu Paul Rubel 2010-09-02 12:43:17

Wow, ça a l'air génial. Je pense qu'expliquer à votre patron, que vous avez besoin de beaucoup de temps pour refactoriser la bête vaut la peine d'essayer. S'il n'est pas d'accord, cesser de fumer 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 ferait que transmettre à ces services et tout nouveau code que vous écrirez les utilisera à la place du"module principal". Cela devrait être possible dans une quantité raisonnable de temps (parce qu'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 si vous avez encore du temps, vous pouvez le passer à refactoriser tous les anciens modules dépendants pour utiliser également les services globaux.

3
répondu back2dos 2010-09-04 09:15:14

Mes sympathies-dans mon travail précédent, j'ai rencontré une situation similaire avec un fichier qui était plusieurs fois plus grand 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'aurez pas déjà en main...
  2. identifiez un code qui peut être extrait dans une classe helper / utilities. Pas besoin d'être grand, juste quelque chose qui ne fait pas vraiment partie de votre classe "principale".
  3. refactoriser le code identifié en 2. dans une classe séparée.
  4. relancez vos tests pour vous assurer que rien n'est cassé.
  5. Quand vous avez le temps, goto 2. et répétez au besoin pour rendre le code gérable.

Les classes que vous construisez à l'étape 3. les itérations vont probablement croître pour absorber plus de code qui est approprié à leur fonction nouvellement claire.

Je pourrais aussi ajouter:

0: achetez Le Livre de Michael Feathers sur le travail avec le code hérité

Malheureusement, ce type de travail est trop commun, mais mon expérience est qu'il y a une grande valeur à être capable de rendre le code de travail mais horrible progressivement moins horrible tout en le gardant fonctionnant.

3
répondu Steve Townsend 2010-09-07 17:00:30

Envisager des moyens de réécrire l'application entière d'une manière plus raisonnable. Peut-être réécrire une petite section de comme un prototype pour voir si votre idée est faisable.

Si vous avez identifié une solution réalisable, 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
répondu wallyk 2010-09-01 07:32:03

Mes de 0,05 centimes d'euro:

Re-concevoir 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èles avec des bases de code potentiellement différentes pour chacune, il y a évidemment un besoin de modifiabilité élevée, 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 troublées. Séparez les pièces les plus changeantes pour leurs propres modules (par exemple dll) de telle sorte 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 ayant le noyau inchangé.

La refonte devra probablement être un projet séparé, essayer de le faire à une cible mobile ne fonctionnera pas.

Quant à l'historique du code source, mon avis: oubliez-le pour le nouveau code. Mais gardez l'histoire quelque part donc vous pouvez le vérifier, si nécessaire. Je parie que tu n'en auras pas tant besoin après le début.

Vous devez probablement obtenir l'adhésion de la direction pour ce projet. Vous pouvez discuter Peut-être avec un temps de développement plus rapide, moins de bugs, un maintien plus facile et moins de chaos global. Quelque chose dans le sens de "activer 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
répondu Slinky 2010-09-01 08:47:44

Commencez par 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 à quel point le code le base. Ensuite, déplacez les bits communs de fonctionnalité ensemble. Petits changements à la fois.

2
répondu Jesper Smith 2010-09-02 10:12:14

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

2
répondu Derek 2010-09-02 14:56:36

Quelque chose que je trouve utile de faire (et je le fais maintenant bien que pas à l'échelle que vous rencontrez), 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
répondu Channing Walton 2010-09-02 21:00:40

J'ai trouvé cette phrase pour être 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 vraiment 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 branches (une pour chacune de vos versions de maintenance).

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

Maintenant, à la branche sur laquelle vous travaillez-refactorisez-la comme bon vous semble, en sachant que vous ne dérangerez pas les neuf autres branches de votre produit.

Je serais un peu préoccupé par le fait 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 principaux-comme un simulation ou objet d'application-ces classes est l'endroit où le travail réel devrait continuer.

J'initialiserais également un objet de journalisation d'application dans main pour une utilisation globale dans tout le programme.

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

Tu dis ça

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

Il semble que ces deux tâches pourraient ê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 l'adhésion stricte à votre flux de travail SCC cause des problèmes de maintenance logicielle. Laissez tomber, maintenant et ne regardez pas en arrière, parce que dès que vous le réparez, vous commencerez à dormir facilement.

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

2
répondu user206705 2010-09-04 08:02:29

Comme vous l'avez décrit, le problème principal est la différence entre pré-split et post-split, la fusion dans les corrections de bugs, etc.. L'outil autour d'elle. Il ne faudra pas si longtemps pour coder en dur un script en Perl, Ruby,etc. pour arracher la plupart du bruit de la différence pré-split contre une concaténation de post-split. Faites ce qui est le plus facile en termes de manipulation du bruit:

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

Vous pouvez même le faire à chaque fois qu'il y a un checkin, la concaténation s'exécute et vous avez quelque chose prêt à comparer les versions à un seul fichier.

2
répondu Tony D 2010-09-04 09:20:45
  1. Ne touchez plus jamais ce fichier et le code!
  2. traiter est comme quelque chose que vous êtes coincé avec. Commencez à écrire des adaptateurs pour la fonctionnalité encodée là.
  3. Écrivez un nouveau code dans différentes unités et parlez uniquement aux adaptateurs qui encapsulent la fonctionnalité du monstre.
  4. ... si un seul des éléments ci-dessus n'est pas possible, quittez le travail et obtenez-en un nouveau.
2
répondu paul_71 2010-09-05 10:26:30

"le fichier contient essentiellement la" main class " (main internal work dispatching and coordination) de notre programme, donc chaque fois qu'une fonctionnalité est ajoutée, elle affecte également ce fichier et chaque fois qu'il se développe."

Si ce gros Commutateur (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 du commutateur 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
répondu Grozz 2010-09-01 10:52:16

Je pense que le moyen le plus simple de suivre l'historique de la source lors de la division d'un fichier serait quelque chose comme ceci:

  1. Faites des copies du code source d'origine, en utilisant toutes les commandes de copie préservant l'historique fournies par votre système SCM. Vous aurez probablement besoin de soumettre à ce stade, mais il n'y a pas encore besoin de dire à votre système de construction sur les nouveaux fichiers, donc cela devrait être ok.
  2. supprimer le code de ces copies. Cela ne devrait pas briser l'histoire pour les lignes que vous gardez.
1
répondu Christopher Creutzig 2010-09-01 14:52:28

Je pense que ce que je ferais dans cette situation est de mordre la balle et:

  1. comprendre 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.rpc après 5pm vendredi!!!"
  3. passez votre long week - end à appliquer cette modification aux versions de maintenance >10 (de la plus ancienne à la plus récente), jusqu'à la version actuelle incluse.
  4. Supprimer mainmodule.cpp de toutes les versions prises en charge du logiciel. C'est un nouvel âge - il n'y a plus de mainmodule.rpc.
  5. convaincre la direction que vous ne devriez 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 ont leur propre version.... yeeeeeshhhh. J'ajouterais des directives de compilateur plutôt que d'essayer de maintenir 10+ forks.

Le suivi des anciennes modifications apportées au fichier est simplement résolu par votre premier commentaire d'enregistrement en disant quelque chose comme " split de mainmodule.rpc". Si vous avez besoin de 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 plus de 2 ans pour regarder qui a changé le code et pourquoi?

1
répondu BIBD 2010-09-01 17:22:42