Maintenir des centaines de branches personnalisées sur la branche principale


140

Actuellement, nous avons une branche principale pour notre application PHP dans un référentiel partagé. Nous avons plus de 500 clients abonnés à notre logiciel. La plupart d’entre eux peuvent être personnalisés à des fins différentes, chacun dans une branche distincte. La personnalisation peut être un nom de champ de texte différent, une fonctionnalité ou un module totalement nouveau, ou de nouvelles tables / colonnes dans la base de données.

Le défi auquel nous sommes confrontés est qu’au fur et à mesure que nous maintenons ces centaines de succursales personnalisées et les distribuons aux clients, nous fournissons de temps en temps de nouvelles fonctionnalités et mettons à jour notre branche principale. les à la dernière version.

Malheureusement, cela entraîne souvent de nombreux conflits dans le code personnalisé et nous passons de nombreuses heures à parcourir chaque branche pour résoudre tous les conflits. C'est très inefficace et nous avons constaté que les erreurs ne sont pas rares lors de la résolution de ces conflits.

Je recherche un moyen plus efficace de maintenir nos succursales de publication clientes à jour avec la succursale principale, ce qui nécessitera moins d'effort lors de la fusion.


11
Désolé de ne pas donner une réponse "vous pouvez utiliser l'outil X", mais il n'y en a pas.
Courses de légèreté en orbite

3
Ou pendant la construction (ce qui est probablement plus courant). Juste .. pas tout à fait séparément les bases de code.
Courses de légèreté en orbite

15
@FernandoTan - Votre symptôme visible peut être du code, mais la cause première de votre maladie est la fragmentation de votre produit. Le remède doit provenir de la cartographie du produit / de la capacité du produit, et non du nettoyage du code - cela finira par se produire. J'ai détaillé plus dans ma réponse - programmers.stackexchange.com/a/302193/78582
Alex S

8
Cela pourrait aussi être un problème économique. Gagnez-vous vraiment de l’argent avec l’ensemble de ces 500 clients? Sinon, vous devez trop penser à votre modèle de tarification et rejeter les demandes de modification si le client ne paie pas de frais supplémentaires.
Christian Strempfer

13
Cela a fait mon cœur se briser un tout petit peu. Heureusement, d'autres personnes crient déjà les bonnes réponses - ma seule recommandation supplémentaire est que vous rédigiez ceci et le soumettiez à TheDailyWTF.
zxq9

Réponses:


314

Vous abusez complètement des branches! La personnalisation doit être optimisée par la flexibilité de votre application, et non de la souplesse de votre contrôle de version (qui, comme vous l'avez découvert, n'est pas destiné / conçu pour ce type d'utilisation).

Par exemple, faites en sorte que les étiquettes de champs de texte proviennent d'un fichier texte et ne soient pas codées en dur dans votre application (c'est ainsi que fonctionne l'internationalisation). Si certains clients ont des fonctionnalités différentes, rendez votre application modulaire , avec des limites internes strictes régies par des API rigoureuses et stables, afin que les fonctionnalités puissent être connectées à la demande .

L'infrastructure de base et toutes les fonctionnalités partagées ne doivent alors être stockées, maintenues et testées qu'une seule fois .

Vous auriez dû le faire depuis le début. Si vous avez déjà cinq cents variantes de produit (!), Résoudre ce problème sera un travail énorme … mais pas plus que la maintenance en cours.


142
+1 pour "Vous devriez avoir fait cela depuis le début". Ce niveau de dette technique peut détruire une entreprise.
Daenyth

31
@ Daenyth: Franchement, avec cinq cents branches personnalisées, je suis étonné de ne pas l'avoir déjà fait. Qui laisse les choses aller si mal? lol
Courses de légèreté en orbite

73
@FernandoTan Je suis tellement, tellement, tellement désolé pour vous ...
enderland

20
@ FernandoTan: Moi aussi. :( Peut-être auriez-vous dû poser plus de questions lors de l'entretien?;) Pour être clair, le "vous" dans ma réponse est l'organisation. C'est une abstraction. Je ne cherche pas à blâmer les individus.
Courses de légèreté en orbite

58
Commencez par obtenir plus d'informations: laissez les développeurs créer un diff entre la version actuelle et la branche personnalisée. Vous savez donc au moins quelles sont les différences. Cette liste vous permet de voir où vous pouvez gagner la réduction de branches la plus rapide. Si 50 ont des noms de champs personnalisés, concentrez-vous dessus et vous économiserez 50 branches. Ensuite, cherchez le suivant. Vous pouvez également en avoir qui ne sont pas récupérables, mais au moins le montant sera inférieur et ne augmentera pas lorsque vous aurez plus de clients.
Luc Franken

93

Avoir 500 clients est un bon problème. Si vous aviez passé du temps à éviter ce problème avec les succursales, vous n’auriez peut-être jamais pu rester en affaires assez longtemps pour obtenir des clients.

Premièrement, j'espère que vous facturez vos clients suffisamment pour couvrir TOUS les coûts de maintenance de leurs versions personnalisées. Je suppose que les clients s'attendent à obtenir de nouvelles versions sans avoir à payer pour que leurs personnalisations soient répétées. Je commencerais par trouver tous les fichiers identiques dans 95% de vos succursales. Ce 95% est la partie stable de votre application.

Recherchez ensuite tous les fichiers ne contenant que quelques lignes différentes entre les branches. Essayez d’introduire un système de configuration tel que ces différences puissent être supprimées. Ainsi, par exemple, plutôt que d'avoir des centaines de fichiers avec des étiquettes de champs de texte différentes, vous disposez d'un fichier de configuration pouvant remplacer n'importe quelle étiquette de texte. (Cela ne doit pas nécessairement être fait en une fois, il suffit de configurer une étiquette de champ de texte configurable la première fois qu'un client veut le changer.)

Passez ensuite aux problèmes les plus difficiles en utilisant le modèle de stratégie, l’injection de dépendance, etc.

Envisagez de stocker json dans la base de données plutôt que d'ajouter des colonnes pour les propres champs du client. Cela peut fonctionner pour vous si vous n'avez pas besoin de rechercher ces champs avec SQL.

Chaque fois que vous archivez un fichier dans une branche, vous DEVEZ le différencier avec principal et justifier chaque modification, y compris les espaces. Beaucoup de changements ne seront pas nécessaires et peuvent être supprimés avant l'enregistrement. Cela peut simplement être dû à un développeur ayant différents paramètres dans son éditeur pour la façon dont le code est formaté.

Votre objectif est d’abord de passer de 500 branches contenant un grand nombre de fichiers différents à la plupart des branches n’ayant que quelques fichiers différents. Tout en gagnant assez d'argent pour vivre.

Vous pouvez toujours avoir 500 succursales dans de nombreuses années, mais si elles sont beaucoup plus faciles à gérer, alors vous avez gagné.


Basé sur le commentaire de br3w5:

  • Vous pouvez prendre chaque cours qui est différent entre les clients
  • Créez une classe «xxx_baseclass» qui définit toutes les méthodes appelées dans la classe de l'extérieur
  • Renommez la classe pour que xxx s'appelle xxx_clientName (en tant que sous-classe de xxx_baseclass)
  • Utilisez l'injection de dépendance de sorte que la version correcte de la classe soit utilisée pour chaque client.
  • Et maintenant, l'intelligence intelligente br3w5 est venue! Utilisez un outil d'analyse de code statique pour rechercher le code désormais dupliqué, puis déplacez-le dans la classe de base, etc.

Ne faites ce que ci-dessus après avoir obtenu le grain facile, et suivez-le d'abord avec quelques classes.


28
+1 pour avoir tenté de fournir une approche au problème actuel
Ian

35
J'étais vraiment inquiet que vous vous félicitiez pour votre réponse, jusqu'à ce que je réalise que vous n'étiez pas le même @Ian qui a écrit la réponse.
Theron Luhn

2
Ils devraient peut-être utiliser un outil d'analyse de code statique pour préciser les parties du code dupliquées (après avoir identifié tous les fichiers identiques)
br3w5

1
Création également de packages versionnés pour aider l’équipe à déterminer quel client a quelle version du code
br3w5

1
Cela ressemble à une longue façon de dire "refactorez votre code"
Roland Tepp

40

À l'avenir, posez les questions du test Joel lors de votre entretien. Vous seriez plus susceptible de ne pas marcher dans une épave de train.


C'est un, ah, comment dirons-nous ... vraiment, vraiment grave problème à avoir. Le "taux d'intérêt" sur cette dette technique va être très très élevé. Ce n'est peut-être pas récupérable ...

Dans quelle mesure ces modifications personnalisées sont-elles intégrées au "noyau"? Pouvez-vous leur faire leur propre bibliothèque et avoir un seul "noyau" et chaque client spécifique ayant son propre "add-on"?

Ou s'agit-il de configurations très mineures?

Je pense que la solution est une combinaison de:

  • Modification de toutes les modifications codées en dur en éléments basés sur la configuration. Dans ce cas, tout le monde a la même application principale, mais les utilisateurs (ou vous) activent / désactivent la fonctionnalité, définissent les noms, etc. selon les besoins.
  • Déplacement de fonctionnalités / modules "spécifiques au client" vers des projets distincts. Ainsi, au lieu d’avoir un "projet", vous avez un "projet principal" avec des modules que vous pouvez ajouter / supprimer facilement. Sinon, vous pouvez également définir ces options de configuration.

Ni l'un ni l'autre ne sera trivial comme si vous vous retrouviez ici avec plus de 500 clients, vous n'avez probablement pas fait de réelle distinction à cet égard. Je prévois que vos changements dans la séparation de cette tâche prendront beaucoup de temps.

Je soupçonne également que vous allez avoir de gros problèmes pour séparer et classer facilement tout votre code spécifique au client.

Si la plupart de vos modifications portent spécifiquement sur des différences de formulation, je suggère de lire des questions telles que celle-ci sur la localisation linguistique. Que vous fassiez plusieurs langues entièrement ou juste un sous-ensemble, la solution est la même. C'est spécifiquement PHP et la localisation.


1
De plus, puisque ce sera une tâche énorme (pour le moins qu'on puisse dire), il sera également difficile de convaincre vos gestionnaires de consacrer beaucoup de temps et d'argent à ce problème. @FernandoTan Il peut y avoir des questions + réponses sur ce site qui peuvent aider avec ce problème spécifique.
Radu Murzea

10
Quelle question du test Joel vous aurait dit que la société abuse de succursales?
SpaceTrucker

2
@SpaceTrucker: "Faites-vous des builds quotidiens?" aurait pu aider. Avec 500 succursales, ils ne les avaient probablement pas, ou auraient peut-être mentionné qu'ils ne le faisaient que pour certaines succursales.
Sleske

17

C’est l’un des pires anti-modèles que vous pouvez utiliser avec un VCS.

La bonne approche consiste à transformer le code personnalisé en un élément piloté par la configuration. Chaque client peut ensuite avoir sa propre configuration, codée en dur dans un fichier de configuration, dans une base de données ou à un autre emplacement. Vous pouvez activer ou désactiver des fonctionnalités complètes, personnaliser l'apparence des réponses, etc.

Cela vous permet de conserver une branche principale avec votre code de production.


3
Si vous faites cela, rendez-vous service et essayez d'utiliser le modèle de stratégie autant que possible. Cela rendra beaucoup plus facile la maintenance de votre code que si vous vous contentez de vous couvrir de bout en bout if(getFeature(FEATURE_X).isEnabled()).
TMN

13

Le but des branches est d’explorer une voie de développement possible sans risquer de casser la stabilité de la branche principale. Ils devraient finalement être fusionnés à un moment opportun ou être jetés s'ils mènent à une impasse. Ce que vous avez ne sont pas tellement des branches, mais bien plutôt 500 fourchettes du même projet et essayer d'appliquer les ensembles de modifications vitales à toutes est une tâche sisyphéenne.

Au lieu de cela, vous devez faire en sorte que votre code principal soit stocké dans son propre référentiel, avec les points d'entrée nécessaires pour modifier le comportement via la configuration et pour injecter le comportement autorisé par les dépendances inversées .

Les différentes configurations que vous avez pour les clients peuvent alors simplement se distinguer par certains états configurés de manière externe (par exemple, une base de données) ou si nécessaire vivre en tant que référentiels séparés, qui ajoutent le noyau en tant que sous-module.


6
Vous avez oublié les branches de maintenance, qui sont à l’opposé de celles que vous avez décrites dans votre réponse. :)
Courses de légèreté en orbite

7

Toutes les choses importantes ont été proposées par de bonnes réponses ici. J'aimerais ajouter mes cinq sous comme une suggestion de processus.

J'aimerais vous suggérer de résoudre ce problème à moyen ou long terme et d'adopter votre politique, la manière dont vous développez le code. Essayez de devenir une équipe d'apprentissage flexible. Si quelqu'un a autorisé 500 repos au lieu de rendre le logiciel configurable, il est temps de se demander comment vous avez travaillé jusqu'à présent et vous le ferez à partir de maintenant.

Ce qui signifie:

  1. Clarifiez les responsabilités en matière de gestion du changement: si un client a besoin d’adaptations, qui le vend, qui le lui permet et qui décide de la manière dont le code sera modifié? Où sont les vis à tourner si certaines choses doivent être des changements?
  2. Clarifiez le rôle, qui dans votre équipe est autorisé à faire de nouveaux repos et qui ne le sont pas.
  3. Essayez de vous assurer que tous les membres de votre équipe comprennent la nécessité de modèles qui permettent une flexibilité des logiciels.
  4. Clarifiez votre outil de gestion: comment savoir rapidement quel client a quel code a été adopté. Je sais, une "liste de 500" semble ennuyeuse, mais voici une "économie émotionnelle", si vous voulez. Si vous ne pouvez pas dire rapidement les modifications du client, vous vous sentez encore plus égaré et dessiné comme si vous deviez commencer une liste. Utilisez ensuite cette liste pour regrouper les fonctionnalités de la manière dont les réponses des autres utilisateurs vous ont montré:
    • regrouper les clients par changements mineurs / majeurs
    • changements liés au groupe par sujet
    • groupe par changements facile à fusionner et changements difficiles à fusionner
    • trouver des groupes de modifications égales apportées à plusieurs pensions (oh oui, il y en aura).
    • peut-être le plus important pour parler à votre responsable / investisseur: groupez par changements coûteux et à moindre coût .

Cela n’a aucunement pour but de créer une atmosphère de mauvaise pression dans votre équipe. Je suggère plutôt que vous clarifiiez d'abord ces points pour vous-même et, où que vous sentiez le soutien, organisez cela avec votre équipe. Invitez des personnes amicales à la table afin d’améliorer votre expérience.

Ensuite, essayez d’établir une fenêtre de temps à long terme, où vous cuisinez cette chose sur une petite flamme. Suggestion: essayez de fusionner au moins deux pensions chaque semaine et supprimez-en au moins un . Vous apprendrez peut-être que souvent, vous pouvez fusionner plus de deux branches à mesure que vous obtenez une routine et une surveillance. De cette façon, en un an, vous pourrez traiter les pires branches (les plus chères?) Et, en deux ans, vous pourrez réduire ce problème et obtenir un logiciel nettement meilleur. Mais ne vous attendez pas à plus, car à la fin, personne n’aura le temps de le faire, mais c’est vous qui ne le permettrez plus car vous êtes l’architecte du logiciel.

C'est comme cela que j'essaierais de le gérer si j'étais à votre place. Cependant, je ne sais pas comment votre équipe va accepter de telles choses, comment le logiciel le permet vraiment, comment vous êtes pris en charge et aussi ce que vous devez encore apprendre. Vous êtes l'architecte logiciel - allez-y :-)


2
Les points positifs sur le traitement des problèmes sociaux / organisationnels cachés derrière les problèmes techniques. Ceci est trop souvent négligé.
Sleske

5

En comparant tous les opposants, supposons un réel besoin commercial.

(Par exemple, le livrable est un code source, les clients appartiennent au même secteur d’activité et sont donc concurrents, et votre modèle d’entreprise promet de garder le secret sur eux.)

De plus, supposons que votre société dispose des outils nécessaires pour gérer toutes les branches, c’est-à-dire soit de la main-d’œuvre (par exemple, 100 développeurs dédiés à la fusion, avec un délai de publication de 5 jours; ou 10 développeurs en supposant qu’un délai de publication de 50 jours est acceptable), ou de tels tests automatisés géniaux que les fusions automatisées sont réellement testées à la fois par rapport aux spécifications principales et aux spécifications d' extension dans chaque branche, et donc uniquement les modifications qui ne fusionnent pas "proprement" nécessitent une intervention humaine. Si vos clients paient non seulement pour des personnalisations, mais pour leur maintenance, il peut s’agir d’un modèle commercial valide.

Ma question (et non-dire) est: avez-vous une personne dédiée responsable de la livraison à chaque client? Si vous êtes, par exemple, une entreprise de 10 000 personnes, cela peut être le cas.

Cela peut être géré par l’ architecture de plug-in dans certains cas, disons que votre cœur est un trunk, que les plug-ins peuvent être conservés dans des trunk ou des branches, et que la configuration de chaque client est un fichier portant un nom unique ou est conservée dans une branche du client.

Les plugins peuvent être chargés au moment de l'exécution ou intégrés à la compilation.

Vraiment, de nombreux projets sont réalisés de la sorte, mais le problème est fondamentalement le même: des modifications de base simples sont faciles à intégrer, les modifications de conflit doivent être soit annulées, soit nécessaires pour de nombreux plug-ins.

Il y a des cas où les plugins ne sont pas assez bons, c'est-à-dire que de nombreux internes du noyau doivent être peaufinés, ce qui fait que le nombre d'interfaces de plugins devient trop important pour être géré.

Idéalement, cela serait géré par une programmation orientée aspect , où trunk est le code principal et les branches sont des aspects (c'est-à-dire du code supplémentaire et des instructions sur la manière de connecter des extras au noyau).

Un exemple simple, vous pouvez spécifier que personnalisé fooest exécuté avant ou après le noyau, klass.fooqu'il le remplace ou qu'il l'enveloppe et qu'il peut modifier l'entrée ou la sortie.

Il existe une tonne de bibliothèques pour cela, mais le problème des conflits de fusion ne disparaît pas - les fusions saines sont gérées par AOP et les conflits nécessitent toujours une intervention humaine.

Enfin, une telle activité doit réellement concerner la maintenance des succursales , à savoir: la fonctionnalité X spécifique au client est-elle si commune qu'il est moins coûteux de la transférer au cœur du système, même si tous les clients n'en paient pas le coût?


3

Vous ne résolvez pas la cause première de la maladie en observant le symptôme. L'utilisation d'une approche de «gestion du code» est symptomatique, mais ne résoudra pas les problèmes pour vous à long terme. La cause fondamentale est le manque de fonctionnalités, de fonctionnalités et de leurs extensions et variantes du produit "bien géré".

Votre code "personnalisé" ne représente rien d'autre que des extensions des fonctionnalités du produit et des modifications apportées aux champs de données .

L’étendue des fonctionnalités personnalisées, leur différence, leur similarité contextuelle ou non joueront un rôle important dans la «désinfection» de la base de code de votre produit.

Plus que la manière dont vous codez et la version, c'est un endroit où la gestion de produit, l'architecture de produit et l'architecture de données entrent en jeu. Sérieusement.

En fin de compte, le code n’est rien d’autre que votre offre de fonctionnalités / services commerciaux et produits à vos clients. C'est pour cela que votre entreprise est payée.

Cela doit être mieux maîtrisé du point de vue des "capacités" que du code.

Vous, votre entreprise et votre produit ne pouvez pas être tout pour tout le monde. Maintenant que vous disposez d'une base de revenus décente de 500 clients, il est temps de vous concentrer sur ce que vous avez l'intention de devenir.

Et si vous proposez plusieurs solutions, il serait judicieux de modulariser les fonctionnalités de vos produits de manière organisée.

Quelle sera l'ampleur et la profondeur de vos produits? Sinon, cela entraînera des problèmes de «qualité de service» et de «dilution et fragmentation du produit» au fur et à mesure de votre progression.

Serez-vous un CRM ou un ERP ou un traitement / envoi de commande ou Microsoft Excel?

Vos extensions existantes doivent être consolidées et harmonisées, comme un grand éditeur logiciel qui intègre et fusionne les produits acquis dans une startup.

Vous devez disposer d'une personne de qualité chargée de la gestion des produits et de l'architecture des données :

  • Branche principale, ses capacités de produit et fonctionnalités de base
  • Fonctionnalités, types et variantes d'extension personnalisés
  • Importance et variation des 'champs personnalisés'

..pour créer une feuille de route d'assimilation et d'harmonisation de tous ces fils / branches de produits en vrac dans le contexte général de votre application principale.

PS: Connectez-vous à moi, je connais une personne qui peut vous aider à résoudre ce problème :)


-5

Je peux comprendre cela. J'ai pris beaucoup de projets. En fait, 90% de notre travail de développement consiste à réparer de tels problèmes. Tout le monde n’est pas parfait, je vous suggère donc d’utiliser le contrôle de version correctement et à l’endroit où vous vous trouvez. Si possible, procédez comme suit.

  • Désormais, lorsqu'un client demande une mise à jour, déplacez-les dans un nouveau référentiel créé.
  • Si vous voulez les fusionner pour les maîtriser, faites-le en premier et résolvez les conflits.
  • Ensuite, gérez leurs problèmes et leurs sprints avec leur référentiel et conservez ceux que vous souhaitez lancer dans master. Cela pourrait mettre plus de pression sur les cycles de publication, mais cela vous évitera de perdre du temps.
  • Conservez une branche principale du référentiel principal pour les nouveaux clients. Le référentiel principal ne devrait contenir que les branches sur lesquelles vous travaillez pour les tâches futures. Les branches héritées peuvent ensuite être supprimées une fois qu'elles sont migrées vers les référentiels clients.

J'ai personnellement importé un référentiel de GitHub avec 40 branches dans Bitbucket et créé 40 référentiels. Cela n'a pris que quatre heures. Cela a été WordPress variations thématiques poussent donc et tirer était rapide.

Il y a de nombreuses raisons pour "ne pas bien faire les choses du premier coup" et je pense que ceux qui les acceptent rapidement et passent à "faire les choses correctement cette fois" auront toujours du succès.


16
Comment plusieurs référentiels faciliteraient-ils la maintenance?
Mathletics

Dans certains cas, comme le nôtre, les clients doivent avoir accès à chaque référentiel et gérer leurs propres problèmes lorsqu'ils deviennent une solution personnalisée afin de disposer de leur propre référentiel, ce qui facilite leur gestion. Cela peut ne pas fonctionner dans de nombreux cas.
Farrukh Subhani
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.