Un de mes amis travaille pour une petite entreprise sur un projet que chaque développeur détesterait: il est poussé à publier le plus rapidement possible, il est le seul qui semble se soucier de la dette technique, le client n'a pas de formation technique, etc.
Il m'a raconté une histoire qui m'a fait réfléchir à la pertinence des modèles de conception dans des projets comme celui-ci. Voici l'histoire.
Nous devions afficher les produits à différents endroits sur le site. Par exemple, les gestionnaires de contenu peuvent afficher les produits, mais également les utilisateurs finaux ou les partenaires via l'API.
Parfois, des informations manquaient dans les produits: par exemple, un groupe d’entre eux n’avait aucun prix lors de la création du produit, mais le prix n’était pas encore précisé. Certains n'avaient pas de description (la description étant un objet complexe avec des historiques de modifications, un contenu localisé, etc.). Certains manquaient d'informations sur l'envoi.
Inspiré par mes lectures récentes sur les modèles de conception, j'ai pensé qu'il s'agissait d'une excellente occasion d'utiliser le modèle d'objet magique . Alors je l'ai fait et tout était lisse et propre. Il suffisait d'appeler
product.Price.ToString("c")
pour afficher le prix ouproduct.Description.Current
pour afficher la description; aucun truc conditionnel requis. Jusqu'au jour où l'intervenant a demandé à l'afficher différemment dans l'API, en disposant d'unnull
fichier JSON. Et aussi différemment pour les gestionnaires de contenu en affichant "Prix non spécifié [changement]". Et j'ai dû assassiner mon motif Null Object bien-aimé, car il n'était plus nécessaire.De la même manière, j'ai dû supprimer quelques usines abstraites et quelques constructeurs. J'ai fini par remplacer mon magnifique motif Facade par des appels directs et laids, car les interfaces sous-jacentes changeaient deux fois par jour pendant trois mois, et même le Singleton me quittait. lorsque les exigences indiquent que l'objet concerné doit être différent en fonction du contexte.
Plus de trois semaines de travail ont consisté à ajouter des motifs, puis à les déchirer un mois plus tard. Mon code est finalement devenu suffisamment spaghetti pour que personne, y compris moi, ne puisse le conserver. Ne vaudrait-il pas mieux ne jamais utiliser ces schémas?
En effet, je devais travailler moi-même sur ce type de projets où les exigences changent constamment et sont dictées par des personnes qui ne tiennent pas vraiment compte de la cohésion ou de la cohérence du produit. Dans ce contexte, votre agilité importe peu, vous apportez une solution élégante à un problème, et lorsque vous le mettez finalement en œuvre, vous découvrez que les exigences ont changé si radicalement que votre solution élégante ne convient pas. plus longtemps.
Quelle serait la solution dans ce cas?
Ne pas utiliser de modèles de conception, arrêter de penser et écrire du code directement?
Il serait intéressant de réaliser une expérience dans laquelle une équipe écrit du code directement, tandis qu'une autre réfléchit à deux fois avant de taper, prenant le risque de jeter le design original quelques jours plus tard: qui sait, peut-être que les deux équipes auraient le même dette technique. En l'absence de telles données, j'affirmerais simplement qu'il ne semble pas correct de taper du code sans réfléchir au préalable lorsque l'on travaille sur un projet de 20 hommes-mois.
Conservez le modèle de conception qui n’a plus de sens et essayez d’ajouter plus de modèles à la situation nouvellement créée?
Cela ne semble pas juste non plus. Les modèles sont utilisés pour simplifier la compréhension du code; mettre trop de motifs, et le code deviendra un gâchis.
Commencez à penser à un nouveau design qui englobe les nouvelles exigences, puis modifiez lentement l'ancien design pour le transformer en nouveau?
En tant que théoricien et celui qui favorise Agile, je suis totalement dedans. En pratique, lorsque vous savez que vous devez revenir au tableau blanc toutes les semaines pour refaire la majeure partie du modèle précédent et que le client n'a tout simplement pas les moyens de le payer, ni le temps d'attendre. , cela ne fonctionnera probablement pas.
Alors, des suggestions?
la source
Réponses:
Je vois quelques fausses hypothèses dans cette question:
Les modèles de conception ne sont pas une fin en soi, ils devraient vous servir et non l'inverse. Si un modèle de conception ne rend pas le code plus facile à mettre en œuvre, ou du moins à être mieux évolutif (cela signifie: plus facile à adapter à des exigences changeantes), alors le modèle manque son but. N'appliquez pas de motifs lorsqu'ils ne facilitent pas la "vie" de l'équipe. Si le nouveau modèle d'objet Null servait votre ami depuis le temps où il l'utilisait, tout irait bien. Si cela devait être éliminé plus tard, cela pourrait être aussi correct. Si le modèle d'objet Null ralentissait la mise en œuvre (correcte), son utilisation était incorrecte. Notez, de cette partie de l'histoire, on ne peut conclure aucune cause de "code spaghetti" jusqu'à présent.
Ce n'est ni son travail ni sa faute! Votre travail consiste à veiller à la cohésion et à la cohérence. Lorsque les exigences changent deux fois par jour, votre solution ne devrait pas consister à sacrifier la qualité du code. Dites simplement au client combien de temps cela prend et si vous pensez que vous avez besoin de plus de temps pour concevoir le projet "correctement", ajoutez une marge de sécurité suffisante à toute estimation. Surtout lorsque vous avez un client qui essaie de faire pression sur vous, utilisez le "principe de Scotty" . Et quand vous vous disputez avec un client non technique au sujet de l'effort, évitez les termes tels que "refactoring", "tests unitaires", "modèles de conception" ou "documentation de code" - des choses qu'il ne comprend pas et qu'il considère probablement comme "inutiles". un non-sens "parce qu'il n'y voit aucune valeur. ou du moins compréhensible pour le client (fonctionnalités, sous-fonctionnalités, changements de comportement, documents utilisateur, corrections d'erreur, optimisation des performances, etc.).
Honnêtement, si "les interfaces sous-jacentes changent deux fois par jour pendant trois mois", la solution ne devrait pas être de réagir en modifiant le code deux fois par jour. La vraie solution consiste à demander pourquoi les exigences changent si souvent et s'il est possible de faire un changement à cette étape du processus. Peut-être qu'une analyse plus en amont aidera. Peut-être que l'interface est trop large parce que la limite entre les composants est mal choisie. Parfois, il est utile de demander plus d’informations sur la partie des exigences qui sont stables et celles qui sont encore en cours de discussion (et reporte effectivement la mise en œuvre des éléments en discussion). Et parfois, certaines personnes doivent simplement être "frappées dans le cul" pour ne pas changer d'avis deux fois par jour.
la source
Mon humble avis est qu'il ne faut pas éviter ou éviter d'utiliser des motifs.
Les modèles de conception sont simplement des solutions bien connues et fiables aux problèmes généraux, qui ont reçu un nom. Ils ne sont pas différents sur le plan technique de toutes les autres solutions ou conceptions imaginables.
Je pense que le problème réside peut-être dans le fait que votre ami pense "appliquer ou non un modèle de conception", au lieu de "quelle est la meilleure solution à laquelle je puisse penser, y compris, mais sans s'y limiter, les modèles Je sais".
Peut-être que cette approche l’amène à utiliser des modèles de manière partiellement artificielle ou forcée, dans des endroits où ils n’appartiennent pas. Et c'est ce qui résulte en désordre.
la source
Dans votre exemple d'utilisation du modèle Null Object, je crois qu'il a finalement échoué car il répondait aux besoins du programmeur et non à ceux du client. Le client devait afficher le prix sous une forme appropriée au contexte. Le programmeur avait besoin de simplifier une partie du code d'affichage.
Ainsi, lorsqu'un modèle de conception ne répond pas aux exigences, dit-on que tous les modèles de conception sont une perte de temps ou disons-nous qu'il nous faut un modèle de conception différent?
la source
Il semblerait que l’erreur était davantage de supprimer les objets de modèle que de les utiliser. Dans la conception initiale, l'objet null semble avoir fourni une solution à un problème. Cela n'a peut-être pas été la meilleure solution.
Être la seule personne à travailler sur un projet vous donne l'occasion de faire l'expérience de tout le processus de développement. Le gros inconvénient est de ne pas avoir quelqu'un pour être votre mentor. Prendre le temps d'apprendre et d'appliquer les meilleures ou les meilleures pratiques est susceptible de porter ses fruits rapidement. L'astuce consiste à identifier quelle pratique apprendre quand.
Le chaînage des références sous la forme product.Price.toString ('c') constitue une violation de la loi de Demeter . J'ai vu toutes sortes de problèmes avec cette pratique, dont beaucoup sont liés à des valeurs nulles. Une méthode telle que product.displayPrice ('c') pourrait gérer les prix nuls en interne. De même, product.Description.Current pourrait être géré par product.displayDescription (), product.displayCurrentDescription (). ou product.diplay ('Actuel').
La gestion de la nouvelle exigence pour les gestionnaires et les fournisseurs de contenu doit être gérée en tenant compte du contexte. Il existe une variété d'approches qui peuvent être utilisées. Les méthodes d'usine peuvent utiliser différentes classes de produits en fonction de la classe d'utilisateurs à laquelle elles seront affichées. Une autre approche consisterait pour les méthodes d’affichage de classe de produit à construire différentes données pour différents utilisateurs.
La bonne nouvelle est que vous réalisez que les choses deviennent incontrôlables. J'espère qu'il a le code en contrôle de révision. Cela lui permettra d’annuler les mauvaises décisions qu’il prendra invariablement. Une partie de l'apprentissage consiste à essayer différentes approches, dont certaines vont échouer. S'il peut gérer les prochains mois, il trouvera peut-être des solutions qui lui simplifieront la vie et permettront de nettoyer les spaghettis. Il pourrait essayer de réparer une chose chaque semaine.
la source
La question semble être fausse à beaucoup de points. Mais les plus flagrants sont:
Beaucoup de gens ont dit, à juste titre, que les modèles de conception reposent essentiellement sur l’étiquetage et la désignation d’une pratique courante. Alors pensez à une chemise, une chemise a un col, pour quelque raison que ce soit, vous enlevez le col ou une partie du col. La dénomination et l'étiquetage changent, mais il s'agit toujours d'une chemise. C'est exactement le cas ici, des changements mineurs dans les détails ne signifiant pas que vous avez "assassiné" ce schéma. (encore une fois l'esprit la formulation extrême)
D'après mon expérience, lorsque des exigences mineures apparaissent, il vous suffit de modifier une petite partie de la base de code. Certains peuvent être un peu hacky, mais rien de trop grave pour affecter de manière substantielle la maintenabilité ou la lisibilité et souvent quelques lignes de commentaires pour expliquer la partie hacky suffiront. C'est aussi une pratique très courante.
la source
Arrêtons-nous un instant pour examiner la question fondamentale: créer un système dans lequel le modèle d'architecture est trop couplé à des fonctionnalités de bas niveau dans le système, ce qui provoque une rupture fréquente de l'architecture dans le processus de développement.
Je pense que nous devons nous rappeler que l'utilisation de l'architecture et des modèles de conception qui s'y rapportent doit être posée à un niveau approprié, et que l'analyse du niveau approprié n'est pas triviale. D'une part, vous pouvez facilement maintenir l'architecture de votre système à un niveau trop élevé avec uniquement des contraintes très élémentaires telles que "MVC", ce qui peut conduire à des occasions manquées, telles que des directives claires et un effet de levier du code, et à un code spaghetti facilement. s'épanouir dans tout cet espace libre.
D’autre part, vous pourriez aussi bien sur-architecturer votre système, par exemple en fixant les contraintes à un niveau détaillé, en supposant que vous puissiez compter sur des contraintes qui sont en réalité plus volatiles que ce à quoi vous vous attendiez. vous obliger à constamment remodeler et reconstruire, jusqu'à ce que vous commencez à désespérer.
Les modifications apportées aux exigences d’un système seront toujours présentes, dans une mesure plus ou moins grande. Et les avantages potentiels de l’utilisation de l’architecture et des modèles de conception seront toujours présents. Il n’est donc pas vraiment question d’utiliser des modèles de conception ou non, mais à quel niveau vous devriez les utiliser.
Cela nécessite non seulement de comprendre les exigences actuelles du système proposé, mais également d'identifier quels en sont les aspects qui peuvent être vus comme des propriétés de base stables du système et quelles propriétés sont susceptibles de changer au cours du développement.
Si vous constatez que vous devez constamment vous battre avec du code spaghetti non organisé, vous ne faites probablement pas assez d’architecture, ni à un niveau élevé. Si vous constatez une rupture fréquente de votre architecture, vous utilisez probablement une architecture trop détaillée.
L’utilisation de l’architecture et des modèles de design n’est pas une chose dans laquelle vous pouvez simplement «enduire» un système, comme si vous peigniez un bureau. Ce sont des techniques qui doivent être appliquées judicieusement, à un niveau où les contraintes sur lesquelles vous devez compter ont de fortes chances d’être stables, et où ces techniques valent vraiment la peine de modéliser l’architecture et de mettre en œuvre les contraintes / architecture / modèles actuels. comme code.
En ce qui concerne la question d’une architecture trop détaillée, vous pouvez également déployer beaucoup d’efforts dans le domaine de l’architecture où elle n’apporte pas beaucoup de valeur. Voir l’architecture axée sur les risques pour référence, j’aime ce livre - Juste assez d’architecture logicielle , peut-être que vous aussi.
modifier
Clarifié ma réponse car je réalisais que je m'exprimais souvent comme "trop d'architecture", où je voulais vraiment dire "architecture trop détaillée", ce qui n'est pas exactement la même chose. Une architecture trop détaillée peut probablement être souvent considérée comme une architecture "trop", mais même si vous maintenez l’architecture à un bon niveau et créez le plus beau système que l’humanité ait jamais vu, il se peut que cela demande trop d’efforts en architecture si les priorités sont les mêmes. sur les fonctionnalités et les délais de commercialisation.
la source
Votre ami semble faire face à de nombreux vents contraires basés sur son anecdote. C'est malheureux et cela peut être un environnement très difficile. En dépit de la difficulté, il était sur la bonne voie pour utiliser des modèles afin de lui faciliter la vie, et il est dommage qu'il l'ait quitté. Le code spaghetti est le résultat final.
Comme il existe deux problèmes différents, technique et interpersonnel, je traiterai chacun séparément.
Interpersonnel
Votre ami a du mal à s'adapter à l'évolution rapide des exigences et à son impact sur sa capacité à écrire du code maintenable. Je dirais d’abord que les exigences qui changent deux fois par jour, chaque jour pendant une aussi longue période constituent un problème plus grave et suscitent des attentes implicites irréalistes. Les exigences changent plus rapidement que le code ne peut changer. Nous ne pouvons pas nous attendre à ce que le code, ou le programmeur, continue. Ce rythme rapide de changement est symptomatique d'une conception incomplète du produit souhaité à un niveau supérieur. C'est un problème. S'ils ne savent pas ce qu'ils veulent vraiment, ils vont perdre beaucoup de temps et d'argent pour ne jamais l'obtenir.
Il serait peut-être bon de définir des limites pour les changements. Regroupez les modifications dans des ensembles toutes les deux semaines, puis congelez-les pendant les deux semaines de leur mise en œuvre. Construisez une nouvelle liste pour les deux prochaines semaines. J'ai le sentiment que certains de ces changements se chevauchent ou se contredisent (par exemple, un va-et-vient entre deux options). Lorsque les changements surviennent rapidement et furieusement, ils ont tous la priorité. Si vous les laissez accumuler dans une liste, vous pouvez travailler avec eux pour organiser et hiérarchiser ce qui est le plus important pour maximiser les efforts et la productivité. Ils verront peut-être que certains de leurs changements sont stupides ou moins importants, donnant ainsi à votre ami un peu de marge de manœuvre.
Ces problèmes ne devraient cependant pas vous empêcher d'écrire un bon code. Mauvais code conduit à des problèmes plus graves. Il faudra peut-être du temps pour passer d’une solution à l’autre, mais le fait même que c’est possible montre les avantages des bonnes pratiques de codage par le biais de modèles et de principes.
Dans un environnement de changements fréquents, la dette technique sera due à un moment donné. Il est de loin préférable de payer pour cela plutôt que de jeter l'éponge et d'attendre que cela devienne trop gros pour être surmonté. Si un motif n'est plus utile, modifiez-le, mais ne revenez pas aux méthodes de codage des cow-boys.
Technique
Votre ami semble bien comprendre les modèles de conception de base. L'objet nul est une bonne approche du problème auquel il était confronté. En vérité, c'est toujours une bonne approche. Il semble avoir du mal à comprendre les principes qui sous-tendent les modèles, le pourquoi de ce qu’ils sont. Sinon, je ne crois pas qu'il aurait abandonné son approche.
(Ce qui suit est un ensemble de solutions techniques qui n’avaient pas été demandées dans la question initiale, mais qui montrent comment nous pourrions adhérer à des modèles à des fins d’illustration.)
Le principe de l'objet null est l'idée d' encapsuler ce qui varie . Nous cachons ce qui change pour ne pas avoir à le gérer partout ailleurs. Ici, l'objet nul encapsulait la variance dans l'
product.Price
instance (je l'appellerai unPrice
objet et un prix d'objet nul le seraNullPrice
).Price
est un objet de domaine, un concept d'entreprise. Parfois, dans notre logique métier, nous ne connaissons pas encore le prix. Ça arrive. Cas d'utilisation parfait pour l'objet null.Price
s ont uneToString
méthode qui affiche le prix, ou une chaîne vide si elle n’est pas connue (ouNullPrice#ToString
renvoie une chaîne vide). C'est un comportement raisonnable. Ensuite, les exigences changent.Nous devons générer un
null
affichage dans la vue API ou une chaîne différente dans la vue des gestionnaires. Comment cela affecte-t-il notre logique métier? Eh bien, ça ne va pas. Dans la déclaration ci-dessus, j'ai utilisé le mot "view" deux fois. Ce mot n'a probablement pas été prononcé explicitement, mais nous devons nous entraîner à entendre les mots cachés dans les exigences. Alors, pourquoi la vue a-t-elle tant d'importance? Parce que cela nous indique où le changement doit réellement se produire: à notre avis.À part : que nous utilisions ou non un framework MVC n'est pas pertinent ici. Alors que MVC a un sens très spécifique pour «View», je l’utilise dans le sens plus général (et peut-être plus applicable) d’un code de présentation.
Nous avons donc vraiment besoin de résoudre ce problème dans la vue. Comment pourrions-nous faire cela? Le moyen le plus simple de le faire serait de faire une
if
déclaration. Je sais que l'objet null avait pour but de supprimer tous les ifs, mais nous devons être pragmatiques. Nous pouvons vérifier la chaîne pour voir si elle est vide et passer:Comment cela affecte-t-il l'encapsulation? La partie la plus importante ici est que la logique de vue est encapsulée dans la vue . De cette manière, nous pouvons complètement isoler nos objets de logique métier / domaine des modifications apportées à la logique de vue. C'est moche, mais ça marche. Ce n'est pas la seule option, cependant.
Nous pourrions dire que notre logique commerciale a légèrement changé en ce sens que nous voulons générer des chaînes par défaut si aucun prix n'est défini. Nous pouvons modifier légèrement notre
Price#ToString
méthode (créer une méthode surchargée). Nous pouvons accepter une valeur de retour par défaut et renvoyer que si aucun prix n'est défini:Et maintenant notre code de vue devient:
Le conditionnel est parti. Cependant, en faire trop, des méthodes de cas particuliers pourraient proliférer dans vos objets de domaine. Cela n'a donc de sens que s'il n'y a que quelques cas.
Nous pourrions plutôt créer une
IsSet
méthode surPrice
laquelle retourne un booléen:Voir la logique:
Nous voyons le retour du conditionnel dans la vue, mais la logique de gestion indique plus clairement si le prix est défini. Nous pouvons utiliser
Price#IsSet
ailleurs maintenant que nous l'avons disponible.Enfin, nous pouvons résumer l’idée de présenter un prix entièrement dans une aide pour la vue. Cela masquerait le conditionnel, tout en préservant l'objet de domaine autant que nous le voudrions:
Voir la logique:
Il y a beaucoup plus de façons de faire les changements (on pourrait généraliser
PriceStringHelper
dans un objet qui retourne un défaut si une chaîne est vide), mais ce sont quelques rapides qui préservent (pour la plupart) les modèles et les principes, comme ainsi que l'aspect pragmatique de ce changement.la source
La complexité d'un motif de conception peut vous déranger si le problème qu'il était censé résoudre disparaît soudainement. Malheureusement, en raison de l’enthousiasme et de la popularité des modèles de conception, ce risque est rarement expliqué. L'anecdote de votre ami aide beaucoup à montrer que les modèles ne rapportent rien. Jeff Atwood a quelques mots de choix sur le sujet.
Documentez les points de variation (ce sont des risques) dans les exigences
Un grand nombre des modèles de conception les plus complexes (l'objet Null, pas tellement) contiennent le concept de variations protégées , c'est-à-dire "Identifiez les points de variation ou d'instabilité prédits; assignez des responsabilités pour créer une interface stable autour d'eux". Adaptateur, visiteur, façade, couches, observateur, stratégie, décorateur, etc. exploitent tous ce principe. Ils "payent" lorsque le logiciel doit être étendu dans la dimension de la variabilité attendue et que les hypothèses "stables" restent stables.
Si vos exigences sont si instables que vos "variations prévues" sont toujours fausses, les schémas que vous appliquez vous causeront de la douleur ou seront au mieux inutilement complexes.
Craig Larman parle de deux possibilités d'appliquer des variantes protégées:
Les deux sont supposés être documentés par les développeurs, mais vous devriez probablement avoir l'engagement du client envers les points de variation.
Pour gérer les risques, vous pouvez dire que tout modèle de conception appliquant la PV doit être lié à un point de variation des exigences signées par le client. Si un client modifie un point de variation dans les exigences, votre conception devra peut-être être radicalement modifiée (car vous avez probablement investi dans la conception pour prendre en charge cette variation). Pas besoin d'expliquer la cohésion, le couplage, etc.
Par exemple, votre client souhaite que le logiciel fonctionne avec trois systèmes de stockage hérités différents. C'est un point de variation que vous concevez. Si le client abandonne cette exigence, vous disposez bien entendu d'une infrastructure de conception inutile. Le client doit savoir que les points de variation coûtent quelque chose.
CONSTANTS
dans le code source sont une forme simple de PVUne autre analogie avec votre question serait de demander si l’utilisation
CONSTANTS
de code source est une bonne idée. En se référant à cet exemple , supposons que le client ait perdu le besoin de mots de passe. Ainsi, laMAX_PASSWORD_SIZE
propagation constante dans votre code deviendrait inutile et constituerait même un obstacle à la maintenance et à la lisibilité. Souhaitez-vous blâmer l'utilisation deCONSTANTS
la raison?la source
Je pense que cela dépend au moins en partie de la nature de votre situation.
Vous avez mentionné des exigences en constante évolution. Si le client dit "Je veux que cette application apicole fonctionne aussi avec les guêpes", cela semble être le genre de situation dans laquelle une conception soignée aiderait à progresser et non à l'empêcher (surtout si vous considérez qu'elle voudra peut-être à l'avenir gardez les mouches des fruits aussi.)
D'un autre côté, si la nature du changement ressemble davantage à "Je souhaite que cette application apicole gère la masse salariale de mon conglomérat de laveries automatiques", aucune quantité de code ne vous sortira de votre trou.
Il n'y a rien de fondamentalement bon dans les modèles de conception. Ce sont des outils comme les autres. Nous ne les utilisons que pour faciliter notre travail à moyen et à long terme. Si un outil différent (tel que la communication ou la recherche ) est plus utile, nous l'utilisons.
la source