Je travaille sur un grand projet de logiciels qui est hautement personnalisé pour divers clients sur le monde. Cela signifie que nous avons peut-être 80% de code commun entre les différents clients, mais aussi beaucoup de code qui doit changer d'un client à l'autre. Dans le passé, nous avons fait notre développement dans des référentiels distincts (SVN) et lorsqu'un nouveau projet a commencé (nous en avons peu, mais les gros clients) ont créé un autre référentiel basé sur le projet passé de la base de code correspondant à nos besoins. Cela a travaillé dans le passé, mais nous avons rencontré plusieurs problèmes:
Nous discutons maintenant de savoir comment résoudre ces problèmes et nous avons jusqu'ici proposé les idées suivantes sur la manière de résoudre ce problème:
Gardez le développement dans des succursales distinctes Mais mieux organiser en disposant d'un référentiel central où les corrections de bugs généraux sont fusionnées et que tous les projets fusionnent des changements de ce référentiel central à la leur part d'une base régulière (par ex. Par jour). Cela nécessite une énorme discipline et beaucoup d'efforts pour la fusion entre les branches. Donc, je ne suis pas convaincu que cela fonctionnera et nous pouvons garder cette discipline, surtout lorsque la pression de temps pose.
abandonner les branches de développement distinctes et disposer d'un référentiel de code central où tout notre code vit et effectue notre personnalisation en ayant des modules et des options de configuration enfichables. Nous utilisons déjà des conteneurs d'injection de dépendance pour résoudre les dépendances de notre code et nous suivons le modèle MVVM dans la plupart de notre code pour séparer de manière proximité la logique commerciale de notre UI.
La deuxième approche semble être plus élégante, mais nous avons de nombreux problèmes non résolus dans cette approche. Par exemple: comment gérer les modifications/ajouts de votre modèle/base de données? Nous utilisons .NET avec cadre d'entité pour avoir des entités fortement dactylographiées. Je ne vois pas comment nous pouvons gérer les propriétés requises pour un client, mais inutile pour un autre client sans encombrer notre modèle de données. Nous pensons à résoudre ce problème dans la base de données en utilisant des tables satellites (avoir une tablette séparée dans laquelle nos colonnes supplémentaires pour une entité spécifiée vivent avec une mappage de 1: 1 à l'entité d'origine), mais ce n'est que la base de données. Comment gérez-vous cela dans le code? Notre modèle de données vit dans une bibliothèque centrale que nous ne pourrions pas étendre à chaque client en utilisant cette approche.
Je suis sûr que nous ne sommes pas la seule équipe qui lutt avec ce problème et je suis choqué de trouver un peu de matériel sur le sujet.
Donc, mes questions sont les suivantes :
Edit :
Merci pour toutes les suggestions. La plupart des idées correspondent à celles que nous avons déjà eues dans notre équipe, mais il est vraiment utile de voir l'expérience que vous aviez avec eux et des conseils pour mieux les mettre en œuvre.
Je ne suis toujours pas sûr de la façon dont nous irons et je ne prends pas la décision (seule), mais je vais passer cela dans mon équipe et je suis sûr que ce sera utile.
Pour le moment, le ténor semble être un seul référentiel utilisant divers modules spécifiques au client. Je ne suis pas sûr que notre architecture est à ce jour ou combien nous devons investir pour le faire en forme, de sorte que certaines choses puissent vivre dans des référentiels séparés pendant un moment, mais je pense que c'est la seule solution à long terme qui fonctionnera.
Alors, merci encore pour toutes les réponses!
On dirait que le problème fondamental n'est pas seulement un entretien du référentiel de code, mais un manque d'architecture appropriée.
Un cadre ou une bibliothèque standard englobe le premier, tandis que celui-ci serait mis en œuvre sous forme de compléments (plugins, sous-classes, di, tout ce qui est logique pour la structure de code).
Un système de contrôle source qui gère les branches et le développement distribué aiderait probablement également; Je suis fan de Mercurial, d'autres préfèrent git. Le cadre serait la branche principale, chaque système personnalisé serait des sous-branches, par exemple.
Les technologies spécifiques utilisées pour implémenter le système (.NET, WPF, peu importe) sont largement sans importance.
Obtenir ce droit est Pas facile, mais c'est critique pour la viabilité à long terme. Et bien sûr, plus vous attendez plus longtemps, plus la dette technique sera grande à traiter.
Vous pouvez trouver le livre Architecture logicielle: principes et modèles d'organisation utile.
Bonne chance!
Une entreprise que j'ai travaillé pour avoir eu le même problème et que l'approche de lutte contre le problème était la suivante: un cadre commun pour tous les nouveaux projets a été créé; Cela inclut toutes les choses qui doivent être identiques dans chaque projet. Par exemple. Formulaire de génération d'outils, exportation vers Excel, journalisation. Des efforts ont été pris pour s'assurer que ce cadre commun n'est amélioré que (lorsqu'un nouveau projet a besoin de nouvelles fonctionnalités), mais jamais fourchue.
Sur la base de ce cadre, le code spécifique au client a été maintenu dans des référentiels séparés. Lorsqu'il est utile ou nécessaire, les correctifs et les améliorations des bugs sont colisés entre les projets (avec toutes les mises en garde décrites dans la question). Cependant, des améliorations utiles globalement dans le cadre commun.
Ayant tout dans une base de code commun pour tous les clients présente des avantages, mais d'autre part, la lecture du code devient difficile quand il y a d'innombrables if
s pour que le programme se comporter différemment pour chaque client.
EDIT: une anecdote pour rendre cela plus compréhensible:
Le domaine de cette société est la gestion de l'entrepôt et une tâche d'un système de gestion d'entrepôt est de trouver un emplacement de stockage gratuit pour les produits entrants. Cela semble facile, mais dans la pratique, beaucoup de contraintes et de stratégies doivent être observées.
À un moment donné, la direction a demandé à un programmeur de créer un module flexible et paramétré pour trouver des emplacements de stockage, qui ont mis en œuvre plusieurs stratégies différentes et auraient dû être utilisées dans tous les projets suivants. L'effort noble a abouti à un module complexe, qui était très difficile à comprendre et à entretenir. Dans le prochain projet, le projet de projet ne pouvait pas comprendre comment le faire fonctionner dans cet entrepôt et que le développeur dudit module était parti, alors il finit par l'ignorer et écrivit un algorithme personnalisé pour cette tâche.
Quelques années plus tard, la mise en page de l'entrepôt où ce module a été utilisé à l'origine modifié et le module avec toute sa flexibilité ne correspondait pas aux nouvelles exigences; Alors je l'ai remplacé par un algorithme personnalisé là-bas.
Je sais que LOC n'est pas une bonne mesure, mais de toute façon: la taille du module "flexible" était ~ 3000 LOC (PL/SQL), tandis qu'un module personnalisé de la même tâche prend ~ 100..250 loc. Par conséquent, essayer d'être flexible extrêmement augmenté la taille de la base de code, sans gagner la réutilisabilité que nous espérions.
L'un des projets que j'ai travaillé sur plusieurs plates-formes supportées (plus de 5) sur un grand nombre de versions de produits. Beaucoup de défis que vous décrivez étaient des choses que nous avons confrontées, bien que de manière légèrement différente. Nous avions une DB propriétaire, nous n'avions donc pas les mêmes types de problèmes dans cette arène.
Notre structure était similaire à celle de la vôtre, mais nous avons eu un seul référentiel pour notre code. Le code spécifique de la plate-forme est entré dans ses propres dossiers de projet dans l'arborescence du code. Le code commun a vécu dans l'arborescence en fonction de la couche qu'il appartenait.
Nous avons eu une compilation conditionnelle, basée sur la plate-forme construite. Le maintien de la douleur était une sorte de douleur, mais il fallait seulement être fait lorsque de nouveaux modules ont été ajoutés à la couche spécifique à la plate-forme.
Avoir tout le code dans un seul référentiel vous a permis de faire des corrections de bugs sur plusieurs plates-formes et versions en même temps. Nous avons eu un environnement de construction automatisé pour toutes les plates-formes servant de backstop dans le cas où le nouveau code cassé une plate-forme non liée présumée.
Nous avons essayé de le décourager, mais il y aurait des cas où une plate-forme avait besoin d'une solution basée sur un bogue spécifique à la plate-forme dans un code sinon commun. Si nous pouvions remplacer de manière conditionnelle la compilée sans que le module soit fugeux, nous le ferions d'abord. Sinon, nous déplacerions le module du territoire commun et le pousserait à la plate-forme spécifique.
Pour la base de données, nous avons eu quelques tables qui avaient des colonnes/modifications spécifiques à la plate-forme. Nous veillerions à ce que chaque version de la plate-forme de la table a atteint un niveau de base de fonctionnalité, le code commun puisse y référer sans se soucier des dépendances de la plate-forme. Des requêtes/manipulations spécifiques à la plate-forme ont été poussées dans les couches de projet de plate-forme.
Donc, pour répondre à vos questions:
La deuxième approche semble être plus élégante, mais nous avons de nombreux problèmes non résolus dans cette approche.
Je suis sûr que l'un de ces problèmes peut être résolu, l'un après l'autre. Si vous êtes coincé, demandez ici ou sur SO sur le problème spécifique.
Comme d'autres l'ont souligné, la possibilité de préférer un référentiel de code central/un seul référentiel. J'essaie de répondre à votre exemple de question.
Par exemple: comment gérer les modifications/ajouts de votre modèle/base de données? Nous utilisons .NET avec cadre d'entité pour avoir des entités fortement dactylographiées. Je ne vois pas comment nous pouvons gérer les propriétés requises pour un client, mais inutile pour un autre client sans encombrer notre modèle de données.
Il y a des possibilités, tous j'ai vu dans des systèmes du monde réel. Lequel choisir dépend de votre situation:
introduisez des tables "Customattributes" (décrivant des noms et des types) et "CustomattribuValues" (pour les valeurs, par exemple stockées comme une représentation à chaîne, même si elles sont des chiffres). Cela permettra d'ajouter de tels attributs au moment de l'installation ou à l'exécution de l'heure, ayant des valeurs individuelles pour chaque client. N'insiste pas d'avoir chaque attribut personnalisé modélisé "visiblement" dans votre modèle de données.
maintenant, il convient de préciser comment utiliser cela dans le code: il suffit de recevoir un code général pour accéder à ces tableaux et un code individuel (peut-être dans une DLL plug-in distincte, qui est à vous) pour interpréter correctement les attributs correctement
Et pour le code spécifique: vous pouvez également essayer d'introduire une langue de script dans votre produit, en particulier pour l'ajout de scripts spécifiques au client. De cette façon, vous créez non seulement une ligne claire entre votre code et votre code spécifique au client, vous pouvez également permettre à vos clients de personnaliser le système dans une certaine mesure.
J'ai travaillé sur un système plus petit (20 kloc) et j'ai constaté que DI et la configuration sont à la fois de grandes façons de gérer les différences entre les clients, mais pas suffisamment pour éviter de forger le système. La base de données est divisée entre une pièce spécifique à une application, qui comporte un schéma fixe et la partie dépendante du client, définie via un document de configuration XML personnalisé.
Nous avons gardé une seule branche dans Mercurial qui est configuré comme s'il était livrable, mais de marque et configuré pour un client de fiction. Les correctifs de bugs sont mis à jour dans ce projet et le nouveau développement de la fonctionnalité de base ne se produit que. Les communiqués à des clients réels sont des branches de cela, stockées dans leurs propres référentiels. Nous gardons une trace de gros changements au code via des numéros de version écrits manuellement et sur les corrections de bogues en utilisant des nombres de validation.
J'ai peur que je n'ai pas d'expérience directe du problème que vous décrivez, mais j'ai des commentaires.
La deuxième option, de rapprocher le code dans un référentiel central (autant que possible) et de l'architecture de la personnalisation (à nouveau, autant que possible) est presque certainement la voie à suivre à long terme.
Le problème est de savoir comment vous envisagez d'y arriver, et combien de temps cela va prendre.
Dans cette situation, il est probablement acceptable de (temporairement) avoir plus d'une copie de l'application dans le référentiel à la fois.
Cela vous permettra de vous déplacer progressivement à une architecture qui prend directement en charge la personnalisation sans avoir à le faire dans un coup de foudre.
Quand on me demande de commencer le développement de B qui partage 80% de fonctionnalités avec A, je vais soit:
Vous avez choisi 1, et cela ne semble pas bien adapter votre situation. Votre mission est de prévoir lequel des 2 et 3 est un meilleur ajustement.
Je n'ai construit qu'une telle application. Je dirais que 90% des unités vendues ont été vendues telles qu'aucune modification. Chaque client avait sa propre peau personnalisée et nous avons servi le système dans cette peau. Lorsqu'un MOD est entré dans celui-ci affecté les sections de base que nous avons essayées à l'aide si la ramification. Lorsque le MOD n ° 2 est entré dans la même section, nous avons changé de logique de cas qui permettait une expansion future. Cela semblait gérer la plupart des demandes mineures.
Toutes les autres demandes personnalisées mineures ont été traitées par la mise en œuvre de la logique de cas.
Si les mods étaient deux radicaux, nous avons construit un clone (comprenant séparé) et enveloppé un cas autour de celui-ci pour inclure le module différent.
Corrections de bogues et modifications sur le noyau effectué tous les utilisateurs. Nous avons testé soigneusement le développement avant d'aller à la production. Nous avons toujours envoyé des notifications par e-mail qui ont accompagné des modifications et jamais, jamais, n'ont jamais affiché les changements de production les vendredis ... jamais.
Notre environnement était classique ASP et SQL Server. Nous n'étions pas une boutique de code spaghetti ... Tout était modulaire en utilisant Inclus, les sous-routines et les fonctions.