Gérer les intersections d'entités

11

J'ai récemment constaté de plus en plus de problèmes similaires à ceux expliqués dans cet article sur les intersections de fonctionnalités. Un autre terme pour ce serait les lignes de produits, même si j'ai tendance à les attribuer à des produits réellement différents, alors que je rencontre généralement ces problèmes sous la forme de configurations de produits possibles.

L'idée de base de ce type de problème est simple: vous ajoutez une fonctionnalité à un produit, mais les choses se compliquent d'une manière ou d'une autre en raison d'une combinaison d'autres fonctionnalités existantes. Finalement, QA trouve un problème avec une combinaison rare de fonctionnalités auxquelles personne n'avait pensé avant et ce qui aurait dû être un simple bugfix peut même devenir nécessitant des modifications de conception majeures.

Les dimensions de ce problème d'intersection de caractéristiques sont d'une complexité époustouflante. Disons que la version actuelle du logiciel a des Nfonctionnalités et que vous ajoutez une nouvelle fonctionnalité. Simplifions également les choses en disant que chacune des fonctionnalités peut être activée ou désactivée uniquement, alors vous avez déjà 2^(N+1)des combinaisons de fonctionnalités possibles à considérer. En raison d'un manque de meilleurs termes de formulation / recherche, je me réfère à l'existence de ces combinaisons comme problème d'intersection de fonctionnalités . (Points bonus pour une réponse comprenant des références pour un terme plus établi.)

Maintenant, la question avec laquelle je lutte est de savoir comment traiter ce problème de complexité à chaque niveau du processus de développement. Pour des raisons évidentes de coût, il n'est pas pratique, au point d'être utopique, de vouloir aborder chaque combinaison individuellement. Après tout, nous essayons de nous éloigner des algorithmes de complexité exponentielle pour une bonne raison, mais transformer le processus de développement lui-même en un monstre de taille exponentielle est susceptible de conduire à un échec total.

Alors, comment obtenez-vous le meilleur résultat d'une manière systématique qui n'explose aucun budget et est complète de manière décente, utile et professionnellement acceptable.

  • Spécification: lorsque vous spécifiez une nouvelle fonctionnalité - comment vous assurez-vous qu'elle joue bien avec tous les autres enfants?

    Je peux voir que l'on pourrait systématiquement examiner chaque fonctionnalité existante en combinaison avec la nouvelle fonctionnalité - mais ce serait indépendamment des autres fonctionnalités. Étant donné la nature complexe de certaines fonctionnalités, cette vision isolée est souvent déjà tellement impliquée qu'elle a besoin d'une approche structurée en elle-même, sans parler du 2^(N-1)facteur causé par les autres fonctionnalités que l'on a volontairement ignoré.

  • Implémentation: lorsque vous implémentez une fonctionnalité - comment vous assurez-vous que votre code interagit / intersecte correctement dans tous les cas.

    Encore une fois, je m'interroge sur la complexité même. Je connais diverses techniques pour réduire le potentiel d'erreur de deux entités qui se croisent, mais aucune qui ne serait mise à l'échelle de manière raisonnable. Je suppose cependant qu'une bonne stratégie lors de la spécification devrait éviter le problème lors de la mise en œuvre.

  • Vérification: lorsque vous testez une entité - comment gérez-vous le fait que vous ne pouvez tester qu'une fraction de cet espace d'intersection d'entités?

    Il est assez difficile de savoir que tester une seule fonctionnalité isolément ne garantit rien de près d'un code sans erreur, mais lorsque vous le réduisez à une fraction, 2^-Nil semble que des centaines de tests ne couvrent même pas une seule goutte d'eau dans tous les océans combinés . Pire encore, les erreurs les plus problématiques sont celles qui proviennent de l'intersection de caractéristiques, dont on ne s'attend pas à ce qu'elles entraînent des problèmes - mais comment les tester si vous ne vous attendez pas à une intersection aussi forte?

Bien que j'aimerais savoir comment les autres gèrent ce problème, je m'intéresse principalement à la littérature ou aux articles analysant le sujet de manière plus approfondie. Donc, si vous suivez personnellement une certaine stratégie, ce serait bien d'inclure des sources correspondantes dans votre réponse.

Franc
la source
6
Une architecture d'application judicieusement conçue peut accueillir de nouvelles fonctionnalités sans bouleverser le monde; l'expérience est le grand niveleur ici. Cela dit, une telle architecture n'est pas toujours facile à obtenir du premier coup, et parfois vous devez faire des ajustements difficiles. Le problème des tests n'est pas nécessairement le bourbier que vous faites croire, si vous savez comment encapsuler correctement les fonctionnalités et les fonctionnalités et les couvrir avec des tests unitaires adéquats.
Robert Harvey

Réponses:

6

Nous savions déjà mathématiquement que la vérification d'un programme est impossible en temps fini dans le cas le plus général, en raison du problème d'arrêt. Ce type de problème n'est donc pas nouveau.

En pratique, une bonne conception peut fournir un découplage tel que le nombre d'entités qui se croisent est bien inférieur à 2 ^ N, bien qu'il semble certainement être supérieur à N même dans des systèmes bien conçus.

En ce qui concerne les sources, il me semble que presque tous les livres ou blogs sur la conception de logiciels essaient effectivement de réduire ce 2 ^ N autant que possible, bien que je n'en connaisse aucun qui jette le problème dans les mêmes termes que vous. faire.

Pour un exemple de la façon dont la conception peut aider à cela, dans l'article mentionné, une partie de l'intersection des fonctionnalités s'est produite car la réplication et l'indexation ont toutes deux été déclenchées par l'eTag. S'ils disposaient d'un autre canal de communication pour signaler la nécessité de chacun d'entre eux séparément, ils auraient peut-être pu contrôler plus facilement l'ordre des événements et avoir eu moins de problèmes.

Ou peut être pas. Je ne sais rien de RavenDB. L'architecture ne peut pas empêcher les problèmes d'intersection d'entités si les entités sont vraiment inexplicablement entrelacées, et nous ne pouvons jamais savoir à l'avance que nous ne voulons pas d'une entité qui a vraiment le pire des cas d'intersection 2 ^ N. Mais l'architecture peut au moins limiter les intersections en raison de problèmes de mise en œuvre.

Même si je me trompe à propos de RavenDB et des eTags (et je ne les utilise que pour des raisons d'argument - ce sont des gens intelligents et ils ont probablement raison), il devrait être clair comment l'architecture peut aider. La plupart des modèles dont les gens parlent sont conçus explicitement dans le but de réduire le nombre de changements de code requis par les fonctionnalités nouvelles ou changeantes. Cela remonte à loin - par exemple, «Modèles de conception, éléments de logiciels orientés objet réutilisables», l'introduction indique que «chaque modèle de conception permet à certains aspects de l'architecture de varier indépendamment des autres aspects, ce qui rend un système plus robuste à un type particulier de changement".

Mon point est, on peut avoir une idée du Big O des intersections de fonctionnalités en pratique, en regardant ce qui se passe en pratique. En recherchant cette réponse, j'ai trouvé que la plupart des analyses des points de fonction / effort de développement (c'est-à-dire - la productivité) ont trouvé soit moins que la croissance linéaire de l'effort de projet par point de fonction, soit très légèrement supérieure à la croissance linéaire. Ce que j'ai trouvé un peu surprenant. Cela avait un exemple assez lisible.

Cela (et des études similaires, dont certaines utilisent des points de fonction au lieu de lignes de code) ne prouve pas que l'intersection des fonctionnalités ne se produit pas et cause des problèmes, mais il semble être une preuve raisonnable qu'elle n'est pas dévastatrice dans la pratique.

psr
la source
0

Ce ne sera en aucun cas la meilleure réponse, mais j'ai réfléchi à certaines choses qui recoupent les points de votre question, alors j'ai pensé les mentionner:

Soutien structurel

D'après le peu que j'ai vu, lorsque les fonctionnalités sont boguées et / ou ne s'intègrent pas bien avec les autres, c'est en grande partie à cause du mauvais soutien fourni par la structure / structure de base du programme pour les gérer / les coordonner. Je pense que passer plus de temps à étoffer et à compléter le noyau devrait faciliter l'ajout de nouvelles fonctionnalités.

Une chose que j'ai trouvé être commun dans les applications où je travaille est que la structure d'un programme a été mis en place pour gérer un d'un type d'objet ou d'un processus , mais beaucoup des extensions que nous avons fait ou ne veulent avoir à voir avec la manipulation de plusieurs types. Si cela avait été pris en compte davantage au début de la conception de l'application, cela aurait aidé à ajouter ces fonctionnalités plus tard.

Cela devient assez critique lors de l'ajout de la prise en charge de plusieurs X qui impliquent du code fileté / asynchrone / piloté par les événements, car ces choses peuvent mal tourner assez rapidement - j'ai eu la joie de déboguer un certain nombre de problèmes liés à cela.

Il est probablement difficile de justifier ce type d'effort à l'avance, en particulier pour les prototypes ou les projets ponctuels - même si certains de ces prototypes ou projets ponctuels continuent d'être utilisés à nouveau ou comme (la base du) système final, ce qui signifie que les dépenses en auraient valu la peine à long terme.

Conception

Lors de la conception du noyau d'un programme, commencer avec une approche descendante peut aider à transformer les choses en morceaux gérables et vous permet de vous concentrer sur le domaine du problème; après cela, je pense qu'une approche ascendante devrait être utilisée - cela aidera à rendre les choses plus petites, plus flexibles et mieux à ajouter plus tard. (Comme mentionné dans le lien, faire les choses de cette façon réduit les implémentations des fonctionnalités, ce qui signifie moins de conflits / bogues.)

Si vous vous concentrez sur les blocs de construction de base du système et assurez-vous qu'ils interagissent tous bien, alors tout ce qui est construit en les utilisant se comportera probablement bien et devrait mieux s'intégrer au reste du système.

Quand une nouvelle fonctionnalité est ajoutée, je pense qu'une voie similaire pourrait être prise pour la concevoir comme cela a été fait pour la conception du reste du framework: la décomposer puis remonter. Si vous pouvez réutiliser l'un des blocs d'origine du cadre dans la mise en œuvre de la fonctionnalité, cela serait certainement utile; une fois que vous avez terminé, vous pouvez ajouter tous les nouveaux blocs que vous obtenez de la fonctionnalité à ceux déjà dans le cadre principal, en les testant avec l'ensemble de blocs d'origine - de cette façon, ils seront compatibles avec le reste du système et utilisables par le futur fonctionnalités aussi bien.

Simplifier!

Récemment, j'ai adopté une position minimaliste sur la conception, en commençant par simplifier le problème, puis en simplifiant la solution. Si le temps peut être pris pendant une seconde, simplifiant l'itération de conception sur un projet, je pourrais voir que cela est très utile lors de l'ajout de choses plus tard.

Quoi qu'il en soit, c'est mon 2c.

Paul
la source