Je travaille actuellement sur un projet plus important qui a malheureusement des fichiers où les directives de qualité logicielle n'étaient pas toujours suivies. Cela inclut les gros fichiers (lire les lignes 2000-4000) qui contiennent clairement plusieurs fonctionnalités distinctes.
Maintenant, je veux refactoriser ces gros fichiers en plusieurs petits. Le problème est, car ils sont si gros, plusieurs personnes (moi y compris) sur différentes branches travaillent sur ces fichiers. Je ne peux donc pas vraiment m'éloigner du développement et du remaniement, car la fusion de ces remaniements avec les changements d'autres peuples deviendra difficile.
Nous pourrions bien sûr demander à tout le monde de fusionner pour développer, "geler" les fichiers (c'est-à-dire ne plus permettre à personne de les modifier), refactoriser, puis "dégeler". Mais ce n'est pas vraiment bon non plus, car cela nécessiterait que tout le monde arrête son travail sur ces fichiers jusqu'à ce que le refactoring soit effectué.
Alors, existe-t-il un moyen de refactoriser, n'exiger que personne d'autre arrête de travailler (trop longtemps) ou fusionne ses branches de fonctionnalités pour se développer?
Vous avez bien compris qu'il ne s'agit pas tant d'un problème technique que d'un problème social: si vous voulez éviter des conflits de fusion excessifs, l'équipe doit collaborer de manière à éviter ces conflits.
Cela fait partie d'un problème plus vaste avec Git, dans la mesure où la ramification est très facile mais la fusion peut encore prendre beaucoup d'efforts. Les équipes de développement ont tendance à lancer de nombreuses succursales et sont alors étonnées que leur fusion soit difficile, peut-être parce qu'elles essaient d'émuler le Git Flow sans comprendre son contexte.
La règle générale pour des fusions rapides et faciles est d'empêcher les grandes différences de s'accumuler, en particulier que les branches caractéristiques doivent être de très courte durée (heures ou jours, pas mois). Une équipe de développement capable d'intégrer rapidement leurs modifications verra moins de conflits de fusion. Si un code n'est pas encore prêt pour la production, il peut être possible de l'intégrer mais de le désactiver via un indicateur de fonctionnalité. Dès que le code a été intégré dans votre branche principale, il devient accessible au type de refactoring que vous essayez de faire.
C'est peut-être trop pour votre problème immédiat. Mais il peut être possible de demander à des collègues de fusionner leurs modifications qui ont un impact sur ce fichier jusqu'à la fin de la semaine afin que vous puissiez effectuer le refactoring. S'ils attendent plus longtemps, ils devront gérer eux-mêmes les conflits de fusion. Ce n'est pas impossible, c'est juste un travail évitable.
Vous souhaiterez peut-être également éviter de casser de grandes étendues de code dépendant et effectuer uniquement des modifications compatibles avec l'API. Par exemple, si vous souhaitez extraire certaines fonctionnalités dans un module distinct:
Ce processus en plusieurs étapes peut éviter de nombreux conflits de fusion. En particulier, il n'y aura de conflits que si quelqu'un d'autre modifie également la fonctionnalité que vous avez extraite. Le coût de cette approche est qu'elle est beaucoup plus lente que de tout changer en même temps et que vous disposez temporairement de deux API en double. Ce n'est pas si mal jusqu'à ce que quelque chose d'urgence interrompt ce refactoring, la duplication est oubliée ou dépourvue de priorité, et vous vous retrouvez avec un tas de dettes technologiques.
Mais au final, toute solution vous obligera à vous coordonner avec votre équipe.
Faites le refactoring en étapes plus petites. Supposons que votre gros fichier porte le nom Foo
:
Ajoutez un nouveau fichier vide, Bar
, et validez-le dans "trunk".
Recherchez une petite partie du code dans Foo
qui peut être déplacée vers Bar
. Appliquez le déplacement, mettez à jour à partir du tronc, construisez et testez le code, et validez le "tronc".
Répétez l'étape 2 jusqu'à ce que Foo
et Bar
aient la même taille (ou la taille que vous préférez)
De cette façon, la prochaine fois que vos coéquipiers mettront à jour leurs branches à partir du tronc, ils obtiendront vos modifications en "petites portions" et pourront les fusionner une par une, ce qui est beaucoup plus facile que d'avoir à fusionner une division complète en une seule étape. Il en va de même lorsque, à l'étape 2, vous obtenez un conflit de fusion, car quelqu'un d'autre a mis à jour le tronc entre les deux.
Cela n'éliminera pas les conflits de fusion ou la nécessité de les résoudre manuellement, mais cela limite chaque conflit à une petite zone de code, ce qui est beaucoup plus gérable.
Et bien sûr - communiquez la refactorisation au sein de l'équipe. Informez vos partenaires de ce que vous faites, afin qu'ils sachent pourquoi ils doivent s'attendre à des conflits de fusion pour le fichier particulier.
Vous envisagez de fractionner le fichier en tant qu'opération atomique, mais vous pouvez apporter des modifications intermédiaires. Le fichier est devenu progressivement énorme au fil du temps, il peut progressivement devenir petit avec le temps.
Choisissez une pièce qui n'a pas dû changer depuis longtemps (git blame
peut vous aider) et séparez-le en premier. Obtenez ce changement fusionné dans les branches de tout le monde, puis choisissez la prochaine partie la plus facile à fractionner. Peut-être même que le fractionnement d'une partie est une étape trop importante et que vous devriez d'abord faire un réarrangement dans le gros fichier.
Si les gens ne fusionnent pas fréquemment pour se développer, vous devriez encourager cela, puis après avoir fusionné, profitez-en pour séparer les parties qu'ils viennent de changer. Ou demandez-leur de procéder au fractionnement dans le cadre de l'examen de la demande d'extraction.
L'idée est d'avancer lentement vers votre objectif. Vous aurez l'impression que les progrès sont lents, mais soudain, vous vous rendrez compte que votre code est bien meilleur. Il faut beaucoup de temps pour transformer un paquebot.
Je vais suggérer une solution différente de la normale à ce problème.
Utilisez-le comme un événement de code d'équipe. Demandez à tout le monde d'enregistrer son code qui le peut, puis aidez les autres qui travaillent encore avec le fichier. Une fois que tout le monde a vérifié son code, trouvez une salle de conférence avec un projecteur et travaillez ensemble pour commencer à déplacer les choses dans de nouveaux fichiers.
Vous voudrez peut-être définir un délai spécifique pour cela, afin qu'il ne finisse pas par être une semaine d'arguments sans fin en vue. Au lieu de cela, cela pourrait même être un événement hebdomadaire de 1 à 2 heures jusqu'à ce que vous obteniez tous des résultats satisfaisants. Peut-être n'avez-vous besoin que d'une à deux heures pour refactoriser le fichier. Vous ne le saurez pas avant d'avoir essayé, probablement.
Cela a l'avantage que tout le monde soit sur la même page (sans jeu de mots) avec la refactorisation, mais cela peut également vous aider à éviter les erreurs et à obtenir des commentaires des autres sur les regroupements de méthodes possibles à maintenir, si nécessaire.
Le faire de cette façon peut être considéré comme ayant une révision de code intégrée, si vous faites ce genre de chose. Cela permet à la quantité appropriée de développeurs de se connecter à votre code dès que vous l'avez enregistré et prêt pour leur examen. Vous voudrez peut-être toujours qu'ils vérifient le code pour tout ce que vous avez manqué, mais cela permet de s'assurer que le processus de révision est plus court.
Cela peut ne pas fonctionner dans toutes les situations, équipes ou entreprises, car le travail n'est pas distribué de manière à ce que cela se produise facilement. Il peut également être (incorrectement) interprété comme une mauvaise utilisation du temps de développement. Ce code de groupe nécessite l'adhésion du gestionnaire ainsi que du refactor lui-même.
Pour aider à vendre cette idée à votre responsable, mentionnez le bit de révision du code ainsi que tout le monde sachant où se trouvent les choses depuis le début. Empêcher les développeurs de perdre du temps à rechercher un hôte de nouveaux fichiers peut être utile à éviter. En outre, empêcher les développeurs d'obtenir des PO sur l'endroit où les choses se sont terminées ou "complètement manquantes" est généralement une bonne chose. (Moins il y a de fusion, mieux c'est, OMI.)
Une fois que vous avez refactorisé un fichier de cette façon, vous pourrez peut-être obtenir plus facilement l'approbation de plusieurs refacteurs, si cela a été réussi et utile.
Cependant vous décidez de refaire votre refactor, bonne chance!
La résolution de ce problème nécessite l'adhésion des autres équipes car vous essayez de modifier une ressource partagée (le code lui-même). Cela étant dit, je pense qu'il existe un moyen de "migrer" sans avoir d'énormes fichiers monolithiques sans déranger les gens.
Je recommanderais également ne ciblant pas tous les fichiers volumineux à la fois à moins que le nombre de fichiers volumineux augmente de manière incontrôlable en plus de la taille des fichiers individuels.
La refactorisation de fichiers volumineux comme celui-ci provoque fréquemment des problèmes inattendus. La première étape consiste à empêcher les gros fichiers d'accumuler des fonctionnalités supplémentaires au-delà de ce qui est actuellement dans le master ou dans les branches de développement.
Je pense que la meilleure façon de le faire est d'utiliser des crochets de validation qui bloquent certains ajouts aux gros fichiers par défaut, mais peuvent être annulés avec un commentaire magique dans le message de validation, comme @bigfileok
ou quelque chose. Il est important de pouvoir annuler la politique d'une manière indolore mais traçable. Idéalement, vous devriez pouvoir exécuter le hook de validation localement et il devrait vous dire comment remplacer cette erreur particulière dans le message d'erreur lui-même. En outre, c'est juste ma préférence, mais les commentaires magiques non reconnus ou les commentaires magiques supprimant les erreurs qui ne se déclenchent pas réellement dans le message de validation devraient être un avertissement ou une erreur au moment de la validation afin que vous ne vous entraîniez pas par inadvertance les gens à supprimer les crochets, qu'ils en aient besoin ou non.
Le hook de validation peut rechercher de nouvelles classes ou effectuer d'autres analyses statiques (ad hoc ou non). Vous pouvez également choisir un nombre de lignes ou de caractères 10% plus grand que le fichier actuel et dire que le grand fichier ne peut pas dépasser la nouvelle limite. Vous pouvez également rejeter les validations individuelles qui agrandissent le fichier volumineux par trop de lignes ou trop de caractères ou w/e.
Une fois que le gros fichier cesse d'accumuler de nouvelles fonctionnalités, vous pouvez en refactoriser les éléments une par une (et réduire simultanément les seuils appliqués par les hooks de validation pour l'empêcher de se développer à nouveau).
Finalement, les gros fichiers seront suffisamment petits pour que les crochets de validation puissent être complètement supprimés.