Qu'est-ce qui devrait prévaloir: YAGNI ou un bon design?

76

À quel moment YAGNI devrait- il avoir préséance sur les bonnes pratiques de codage et vice versa? Je travaille sur un projet au travail et je souhaite introduire progressivement de bonnes normes de code à mes collègues (actuellement, il n'y en a pas et tout est un peu bidouillé sans rime ni raison), mais après avoir créé une série de cours (nous ne faites pas de TDD ou, malheureusement, aucun type de test unitaire). J'ai pris du recul et je pensais que cela violait YAGNI car je sais à peu près avec certitude que nous n'exigeons pas la nécessité d'étendre certaines de ces classes.

Voici un exemple concret de ce que je veux dire: J'ai une couche d'accès aux données qui enveloppe un ensemble de procédures stockées, qui utilise un modèle rudimentaire de style Repository avec des fonctions de base CRUD. Comme il existe une poignée de méthodes dont toutes les classes de référentiels ont besoin, j'ai créé une interface générique pour mes référentiels, appelée IRepository. Cependant, j'ai ensuite créé une interface "marqueur" (c'est-à-dire une interface qui n'ajoute aucune nouvelle fonctionnalité) pour chaque type de référentiel (par exemple ICustomerRepository) et la classe concrète l'implémente. J'ai fait la même chose avec une implémentation Factory pour construire les objets métier à partir de DataReaders / DataSets renvoyés par la procédure stockée; la signature de ma classe de référentiel a tendance à ressembler à ceci:

public class CustomerRepository : ICustomerRepository
{
    ICustomerFactory factory = null;

    public CustomerRepository() : this(new CustomerFactory() { }

    public CustomerRepository(ICustomerFactory factory) {
        this.factory = factory;
    }      

    public Customer Find(int customerID)
    {
        // data access stuff here
        return factory.Build(ds.Tables[0].Rows[0]);
    }
}

Ce qui me préoccupe ici, c'est que je viole YAGNI parce que je sais avec une certitude à 99% qu'il n'y aura jamais de raison de donner autre chose que du concret CustomerFactoryà ce référentiel; étant donné que nous n'avons pas de tests unitaires, je n'ai pas besoin de quelque MockCustomerFactorychose de similaire, et le nombre d'interfaces risque de semer la confusion chez mes collègues. D'autre part, l'utilisation d'une implémentation concrète de l'usine semble être une odeur de design.

Existe-t-il un bon moyen de parvenir à un compromis entre une conception logicielle appropriée et une architecture non globale? Je me demande si je dois avoir toutes les "interfaces à implémentation simple" ou si je peux sacrifier un peu de bonne conception et ne disposer que, par exemple, de l'interface de base, puis de celle concrète, sans me soucier de la programmation. interface si la mise en œuvre est que sera jamais utilisé.

Wayne Molina
la source
17
Vous dites "comme nous n'avons pas de tests unitaires, je n'ai pas besoin de MockX", ce qui conduit naturellement à "je n'ai pas besoin de IX, je n'ai besoin que de X". Je vois que le fait que vous n’avez pas de tests unitaires met en évidence le fait que vous avez besoin de IX et de MockX, parce que cela vous aidera à avoir des tests unitaires. N'acceptez pas la réalité de ne pas avoir de test, considérez cela comme un problème temporaire auquel on s'attaquera sur une longue période.
Anthony Pegram le
10
Même s'il est trivial de le rechercher sur Google, quelqu'un devrait mentionner que YAGNI signifie "Vous n'en aurez pas besoin"
jeudi
1
Je pense que si vous écriviez de nouveaux cours comme celui-ci, vous voudriez ajouter des tests unitaires. Même si vos collègues ne les exécutent pas. Au moins plus tard, vous pourrez dire: "Regardez! Mes tests unitaires l'ont intercepté quand vous avez cassé mon code! Voyez à quel point les tests unitaires sont géniaux!" Dans ce cas, il serait utile de le rendre ridicule. (Bien que, je préférerais que l'objet puisse être moqué sans définir d'interface)
Winston Ewert
Les tests unitaires ne me forceraient-ils pas à créer (ou à utiliser) un framework fictif afin de ne pas utiliser les procédures stockées actives? C'est la raison principale pour laquelle j'ai tendance à ne pas ajouter de tests: nous avons chacun une copie locale de la base de données de production que nous testons et que nous écrivons du code.
Wayne Molina
3
@ Anthony Et les moqueries justifient-elles toujours la complexité supplémentaire qu'elle entraîne? Se moquer est un bon outil, mais son utilité doit aussi être comparée aux coûts et parfois, l’échelle est renversée par un excès d’indirection. Bien sûr, il existe des outils pour aider avec la complexité supplémentaire, mais ils ne feront pas disparaître la complexité. Il semble exister une tendance croissante à traiter les «tests à tout prix» comme une donnée. Je crois que c'est faux.
Konrad Rudolph le

Réponses:

75

Existe-t-il un bon moyen de parvenir à un compromis entre une conception logicielle appropriée et une architecture non globale?

YAGNI.

Je pourrais sacrifier un peu de bon design

Fausse hypothèse.

et l'interface de base, puis le béton unique,

Ce n'est pas un "sacrifice". C'est une bonne conception.

S.Lott
la source
72
La perfection est atteinte, non pas lorsqu'il n'y a plus rien à ajouter, mais lorsqu'il ne reste plus rien à enlever. Antoine De St-Exupery
début novembre
5
@Newtopian: les gens ont des opinions mitigées sur les derniers produits Apple. :)
Roy Tinker le
14
J'ai un gros problème avec ce genre de réponses. Est-ce que "X est vrai parce que Y le dit et que Y est bien soutenu par la communauté" un raisonnement valide? Si quelqu'un dans la vraie vie s'emportait contre YAGNI, lui montrerais-tu cette réponse comme argument?
mardi
2
@vemv. Personne ici ne se moque de YAGNI. Tout ce que S.Lott a dit, c'est que la contradiction perçue par le PO entre deux buts louables est une erreur. BTW, connaissez-vous un développeur actif dans le monde du logiciel actuel qui ferait des reproches à YAGNI? Si vous travaillez sur de vrais projets, vous savez que les exigences changent constamment et que les utilisateurs et les responsables ne savent généralement pas ce qu'ils veulent ou ne veulent pas jusqu'à ce que vous les leur présentiez. Mettre en œuvre le nécessaire est BEAUCOUP de travail - pourquoi prendre du temps, de l'énergie et gaspiller de l'argent (ou risquer votre travail) en écrivant un code qui tente de prédire l'avenir?
Vecteur
6
Mon point ne concerne pas YAGNI - il s'agit de la qualité des réponses. Je ne dirais pas que quelqu'un en particulier a été déclamé, cela faisait partie de mon raisonnement. Veuillez le relire.
mardi
74

Dans la plupart des cas, éviter le code dont vous n’avez pas besoin vous aidera à mieux concevoir. La conception la plus facile à gérer et la plus durable est celle qui utilise la plus petite quantité de code simple, bien nommé, qui satisfait aux exigences.

Les conceptions les plus simples sont les plus faciles à faire évoluer. Rien ne tue la facilité de maintenance comme des couches d’abstraction inutiles et trop sophistiquées.

Michael Borgwardt
la source
8
Je trouve «éviter le code YAGNI» ambiguë. Cela pourrait signifier un code dont vous n’avez pas besoin ou un code conforme au principe YAGNI . ( 'Code KISS Cf.)
sehe
2
@sehe: Ce n'est pas du tout ambigu (même si "adhérer au principe de YAGNI" est carrément contradictoire) si vous l'épelez: qu'est-ce que "vous-ne voulez pas, ne va pas avoir besoin de code" signifie peut-être du code qui tu n'auras pas besoin?
Michael Borgwardt le
1
Je vais imprimer cette réponse en gros caractères et la suspendre là où tous les astronautes de l'architecture peuvent la lire. +1!
kirk.burleson
1
+1 Je me souviens encore de mon premier projet d’entreprise auquel je devais apporter un soutien et ajouter de nouvelles fonctionnalités. La première chose que j'ai faite a été de supprimer 32 000 lignes de code inutile d'un programme de 40 000 lignes sans perdre de fonctionnalité. Le programmeur d'origine a été licencié peu de temps après.
EJ Brennan
4
@vemv: lit "inutile" comme "non utilisé actuellement, sauf de manière triviale", c'est-à-dire un cas de YAGNI. Et "sur-ingénierie" est un peu plus spécifique que "mauvais". Concrètement, cela signifie "complexité due à des concepts théoriques ou à des exigences possibles imaginées plutôt qu'à des exigences concrètes et actuelles".
Michael Borgwardt le
62

YAGNI et SOLID (ou toute autre méthode de conception) ne s’excluent pas mutuellement. Cependant, ils sont des opposés presque polaires. Vous n'êtes pas obligé d'adhérer à 100% à l'un ou l'autre, mais il y aura des concessions mutuelles; plus vous regardez un motif hautement abstrait utilisé par une classe à un endroit donné, et dites YAGNI et le simplifiez, moins le dessin devient SOLID. L'inverse peut aussi être vrai; plusieurs fois dans le développement, un design est implémenté SOLIDEMENT "sur la foi"; vous ne voyez pas comment vous en aurez besoin, mais vous avez un pressentiment. Cela pourrait être vrai (et cela est de plus en plus vraisemblable à mesure que vous gagnez de l'expérience), mais cela pourrait également vous mettre dans autant de dettes techniques qu'une approche de type "faites-le sans effort"; au lieu d’une base de code "spaghetti code" DIL, vous pouvez vous retrouver avec "code lasagne", avoir autant de couches que simplement ajouter une méthode ou un nouveau champ de données se transforme en un processus de plusieurs jours qui consiste à patauger dans des proxys de service et des dépendances faiblement couplées avec une seule implémentation. Ou vous pourriez vous retrouver avec le "code ravioli", qui se présente sous la forme de petits morceaux qui bougent vers le haut, le bas, la gauche ou la droite dans l’architecture vous conduisent à travers 50 méthodes avec 3 lignes chacune.

Je l'ai dit dans d'autres réponses, mais le voici: lors du premier passage, faites-le fonctionner. Au deuxième passage, rendez-le élégant. Au troisième passage, rendez-le solide.

Décomposer cela:

Lorsque vous écrivez une ligne de code pour la première fois, cela doit simplement fonctionner. À ce stade, pour autant que vous sachiez, il s’agit d’un événement ponctuel. Donc, vous n'avez aucun point de style pour construire une architecture "tour d'ivoire" à ajouter 2 et 2. Faites ce que vous devez faire et supposez que vous ne le verrez plus jamais.

La prochaine fois que votre curseur s'inscrira dans cette ligne de code, vous avez maintenant réfuté votre hypothèse à partir de la première écriture. Vous revisitez ce code, probablement pour l'étendre ou pour l'utiliser ailleurs, donc ce n'est pas un cas isolé. Maintenant, certains principes de base tels que DRY (ne vous répétez pas) et d'autres règles simples pour la conception de code devraient être mis en œuvre; extraire des méthodes et / ou des boucles de formulaire pour le code répété, extraire des variables pour des expressions ou des littéraux courants, peut-être ajouter des commentaires, mais dans l’ensemble, votre code devrait être auto-documenté. Maintenant, votre code est bien organisé, même s’il est peut-être encore étroitement couplé, et quiconque le regarde peut facilement apprendre ce que vous faites en le lisant, au lieu de le suivre ligne par ligne.

La troisième fois que votre curseur entre ce code, c'est probablement une grosse affaire; soit vous l'étendez encore, soit il devient utile dans au moins trois autres endroits différents de la base de code. À ce stade, il s'agit d'un élément clé, voire essentiel, de votre système et doit être conçu comme tel. À ce stade, vous avez également généralement la connaissance de la façon dont elle a été utilisée jusqu'à présent, ce qui vous permettra de prendre de bonnes décisions de conception concernant la manière de concevoir la conception afin de rationaliser ces utilisations, ainsi que les nouvelles. Maintenant, les règles SOLID doivent entrer dans l’équation; extraire des classes contenant du code avec des objectifs spécifiques, définir des interfaces communes pour toutes les classes ayant des objectifs ou des fonctionnalités similaires, configurer des dépendances faiblement couplées entre les classes et concevoir ces dépendances de manière à pouvoir les ajouter, les supprimer ou les échanger facilement.

À partir de ce moment, si vous avez besoin d'étendre, de réimplémenter ou de réutiliser davantage ce code, tout est joliment présenté et résumé dans le format "boîte noire" que nous connaissons et aimons tous; branchez-le partout où vous en avez besoin, ou ajoutez une nouvelle variante sur le thème en tant que nouvelle implémentation de l'interface sans devoir modifier l'utilisation de cette dernière.

KeithS
la source
Je seconde la génialité.
Filip Dupanović le
2
Oui. Quand j’avais l'habitude de concevoir dès le départ pour une réutilisation / extension, je trouvais que lorsque je voulais le réutiliser ou le prolonger, ce serait d'une manière différente de celle que j'avais anticipée. La prévision est difficile, surtout en ce qui concerne l'avenir. Je soutiens donc votre règle des 3 coups - à ce moment-là, vous avez une idée raisonnable de la façon dont elle sera réutilisée / étendue. NB: une exception est si vous savez déjà comment cela se passera (par exemple, à partir de projets précédents, de connaissances de domaine ou déjà spécifiés).
13
Au quatrième passage, rendez-le génial.
Richard Neil Ilagan
@ KeithS: Il semble que John Carmack ait fait quelque chose de similaire: "le code source de Quake II ... unifie Quake 1, Quake World et QuakeGL dans une belle architecture de code." fabiensanglard.net/quake2/index.php
13ren
+1 Je pense que je vais nommer votre règle: "Refactoring pratique" :)
Songo
31

Au lieu de l'un ou l'autre, je préfère WTSTWCDTUAWCROT?

(Quelle est la chose la plus simple que nous puissions faire qui soit utile et que nous puissions publier jeudi?)

Les acronymes plus simples sont sur ma liste de choses à faire, mais ils ne sont pas une priorité.

Mike Sherrill 'Rappel de chat'
la source
8
Cet acronyme viole le principe de YAGNI :)
riwalk le
2
J'ai utilisé exactement les lettres dont j'avais besoin - ni plus ni moins. De cette façon, je suis un peu comme Mozart. Oui. Je suis le Mozart des acronymes.
Mike Sherrill 'Cat Recall' le
4
Je n'ai jamais su que Mozart était terrible pour faire des acronymes. J'apprends quelque chose de nouveau chaque jour sur un site SE. : P
Cameron MacFarland
3
@ Mike Sherrill 'CatRecall': Peut-être que nous devrions étendre cela à WTSTWCDTUWCROTAWBOF = "Quelle est la chose la plus simple que nous puissions faire qui soit utile, nous pouvons publier le jeudi et ne pas faire une pause vendredi?" ;-)
Giorgio
1
@ Stargazer712 non :) il enfreint POLA.
v.oddou
25

YAGNI et un bon design ne sont pas contradictoires. YAGNI consiste à (ne pas) répondre aux besoins futurs. Une bonne conception consiste à rendre transparent ce que votre logiciel fait actuellement et comment il le fait.

L'introduction d'une usine simplifiera-t-elle votre code existant? Sinon, ne l'ajoutez pas. Si c'est le cas, par exemple lorsque vous ajoutez des tests (ce que vous devriez faire!), Ajoutez-le.

YAGNI consiste à ne pas ajouter de complexité pour prendre en charge les futures fonctions.
Une bonne conception consiste à éliminer la complexité tout en prenant en charge toutes les fonctions actuelles.

Jaap
la source
15

Ils ne sont pas en conflit, vos objectifs sont faux.

Qu'essayez-vous d'accomplir?

Vous voulez écrire un logiciel de qualité, et pour ce faire, vous voulez garder votre base de code petite et ne pas avoir de problèmes.

Maintenant que nous sommes en conflit, comment couvrir tous les cas si nous n'écrivons pas de cas que nous n'allons pas utiliser?

Voici à quoi ressemble votre problème.

entrez la description de l'image ici (quiconque est intéressé, cela s'appelle l' évaporation de nuages )

Alors, qu'est-ce qui motive ça?

  1. Vous ne savez pas ce dont vous n'aurez pas besoin
  2. Vous ne voulez pas perdre de temps et gonfler votre code

Lequel de ces problèmes pouvons-nous résoudre? Eh bien, il semble que ne pas vouloir perdre de temps et le code de ballonnement est un objectif génial et logique. Qu'en est-il de ce premier? Pouvons-nous savoir ce que nous allons devoir coder?

Je travaille sur un projet au travail et je souhaite introduire progressivement de bonnes normes de code à mes collègues (actuellement, il n'y en a pas et tout est un peu bidouillé sans rime ni raison) [...] J'ai pris du recul. et j'ai pensé que cela violait YAGNI car je sais à peu près avec certitude que nous n'avons pas besoin d'étendre certaines de ces classes.

Reformulons tout cela

  • Aucune norme de code
  • Aucune planification de projet en cours
  • Les cow-boys font partout leur foutue chose (et vous essayez de jouer au shérif dans le Far West sauvage), yee haw.

Existe-t-il un bon moyen de parvenir à un compromis entre une conception logicielle appropriée et une architecture non globale?

Vous n'avez pas besoin d'un compromis, vous avez besoin de quelqu'un pour gérer l'équipe qui est compétente et a une vision de l'ensemble du projet. Vous avez besoin de quelqu'un qui peut planifier ce dont vous allez avoir besoin, au lieu que chacun d'entre vous jette des choses dont vous N'AVEZ PAS besoin parce que vous êtes si incertain du futur parce que ... pourquoi? Je vais vous dire pourquoi, c'est parce que personne n'a un plan pour vous tous. Vous essayez d'introduire des normes de code pour résoudre un problème totalement distinct. Votre problème PRIMAIRE que vous devez résoudre est une feuille de route et un projet clairs. Une fois que vous avez cela, vous pouvez dire "les normes de code nous aident à atteindre cet objectif plus efficacement en tant qu'équipe", ce qui est la vérité absolue mais dépasse le cadre de cette question.

Obtenez un chef de projet / équipe capable de faire ces choses. Si vous en avez un, vous devez leur demander une carte et expliquer le problème de YAGNI que ne présente pas de carte. S'ils sont manifestement incompétents, écrivez le plan vous-même et dites: "voici mon rapport sur les choses dont nous avons besoin, veuillez le consulter et laissez-nous savoir votre décision."

Incognito
la source
Nous n'en avons pas, malheureusement. Le responsable du développement est soit plus soucieux d’obtenir des résultats rapides que de respecter les normes / qualité, soit de faire en sorte que les pratiques standard telles que l’utilisation de DataSets bruts partout renvoyés directement à partir de classes, le codage de style VB6 et tout ce qui se trouve dans le code-behind avec une logique dupliquée soient copiés et collés. plus de.
Wayne Molina
C'est bon, ça va être un peu plus lent, mais vous devez alors parler de leurs soucis. Expliquez que ce problème de code YAGNI / Inutless vous fait perdre du temps, suggérez la feuille de route, donnez-lui une feuille de route, expliquez-lui que le travail sera plus rapide. Quand il aura fait son entrée, évitez les problèmes que les normes médiocres ont pour la vitesse de développement, suggérez les meilleurs. Ils veulent que le projet soit terminé, ils ne savent tout simplement pas comment gérer la chose.
Incognito
Euh ..... Je pense que l'objectif principal serait "vous voulez avoir un bon produit de valeur"?
Voir
@sehe ce n'est pas sa décision: p.
Incognito
3
Très bonne réponse. Il fait face à une bataille difficile. Le faire sera difficile s'il n'y a pas de participation de la direction. Et vous avez raison, cette question est prématurée et ne règle pas le problème réel.
Seth Spearman
10

Permettre à votre code d'être étendu avec des tests unitaires ne sera jamais couvert par YAGNI, car vous en aurez besoin. Cependant, je ne suis pas convaincu que vos modifications de conception dans une interface à implémentation unique augmentent réellement la testabilité du code car CustomerFactory hérite déjà d'une interface et peut être échangé contre un MockCustomerFactory à tout moment.

DeadMG
la source
1
+1 pour un commentaire utile sur le problème réel plutôt que sur la bataille cosmique et / ou l'amour entre Good Design et YAGNI.
psr
10

La question présente un faux dilemme. Une application correcte du principe YAGNI n’est pas une chose sans rapport. C'est un aspect d'un bon design. Chacun des principes SOLID sont également des aspects d’un bon design. Vous ne pouvez pas toujours appliquer pleinement tous les principes dans n'importe quelle discipline. Les problèmes du monde réel exercent beaucoup de pression sur votre code, et certains d'entre eux vont dans des directions opposées. Les principes de conception doivent en tenir compte, mais aucune poignée de principes ne peut s’adapter à toutes les situations.

Examinons maintenant chaque principe en sachant que, même s’ils peuvent parfois tirer des directions différentes, ils ne sont pas fondamentalement en conflit.

YAGNI a été conçu pour aider les développeurs à éviter un type de travail particulier: celui qui consiste à construire la mauvaise chose. Pour ce faire, il nous aide à éviter de prendre des décisions erronées trop tôt, sur la base d’hypothèses ou de prévisions concernant ce qui, à notre avis, changera ou sera nécessaire à l’avenir. L'expérience collective nous dit que lorsque nous faisons cela, nous avons généralement tort. Par exemple, YAGNI vous dirait de ne pas créer d'interface dans un but de réutilisation , à moins que vous ne sachiez maintenant que vous avez besoin de plusieurs implémenteurs. De même, YAGNI dirait de ne pas créer de "ScreenManager" pour gérer le formulaire unique dans une application, à moins que vous ne sachiez maintenant que vous allez avoir plusieurs écrans.

Contrairement à ce que beaucoup de gens pensent, SOLID n’est pas une question de réutilisabilité, de généricité ou même d’abstraction. SOLID est conçu pour vous aider à écrire du code préparé pour le changement , sans rien dire sur ce que ce changement pourrait être. Les cinq principes de SOLID créent une stratégie de code du bâtiment flexible sans être trop générique, ni simple sans naïf. Une application correcte du code SOLID génère de petites classes ciblées avec des rôles et des limites bien définis. Le résultat pratique est que, quel que soit le besoin, un minimum de classes doit être touché. Et de même, pour tout changement de code, il y a une quantité minimisée de "répercussions" sur les autres classes.

En regardant votre exemple de situation, voyons ce que YAGNI et SOLID pourraient avoir à dire. Vous envisagez une interface de référentiel commune car tous les référentiels ont la même apparence de l'extérieur. Mais la valeur d'une interface générique commune réside dans la possibilité d'utiliser n'importe lequel des implémenteurs sans avoir besoin de savoir laquelle il s'agit en particulier. À moins que cela ne soit nécessaire ou utile dans votre application, YAGNI vous recommande de ne pas le faire.

Il y a 5 principes SOLID à examiner. S est la responsabilité unique. Cela ne dit rien sur l'interface, mais pourrait en dire autant sur vos classes concrètes. On pourrait faire valoir que la gestion de l’accès aux données proprement dit pourrait être confiée à une ou plusieurs autres classes, tandis que la responsabilité des référentiels est de traduire un contexte implicite (CustomerRepository est un référentiel implicite pour les entités du client) en appels explicites aux utilisateurs. API d'accès aux données généralisées spécifiant le type d'entité client.

O est ouvert-fermé. Cela concerne principalement l'héritage. Cela s'appliquerait si vous essayiez de dériver vos référentiels à partir d'une base commune implémentant des fonctionnalités communes, ou si vous espériez dériver plus loin des différents référentiels. Mais vous ne l'êtes pas, alors ce n'est pas le cas.

L est la substituabilité de Liskov. Ceci s'applique si vous avez l'intention d'utiliser les référentiels via l'interface de référentiel commun. Il impose des restrictions sur l'interface et les implémentations pour assurer la cohérence et éviter une manipulation spéciale pour les différents développeurs. La raison en est que ce traitement spécial compromet le but d'une interface. Il peut être utile d’examiner ce principe car il peut vous empêcher d’utiliser l’interface commune du référentiel. Cela coïncide avec les conseils de YAGNI.

I est la ségrégation d'interface. Cela peut s’appliquer si vous commencez à ajouter différentes opérations de requête à vos référentiels. La ségrégation d'interface s'applique lorsque vous pouvez diviser les membres d'une classe en deux sous-ensembles, l'un étant utilisé par certains consommateurs et l'autre par d'autres, mais aucun consommateur n'utilisera probablement les deux sous-ensembles. Il est conseillé de créer deux interfaces distinctes plutôt qu'une interface commune. Dans votre cas, il est peu probable que l'extraction et la sauvegarde d'instances individuelles soient consommées par le même code que celui utilisé pour les requêtes générales. Il peut donc être utile de les séparer en deux interfaces.

D est l'injection de dépendance. Nous revenons ici au même point que le S. Si vous avez séparé votre consommation de l'API d'accès aux données dans un objet séparé, ce principe dit que plutôt que de simplement créer une instance de cet objet, vous devez la transmettre lorsque vous créez. un référentiel. Cela facilite le contrôle de la durée de vie du composant d'accès aux données, en offrant la possibilité de partager des références à ce dernier entre vos référentiels, sans qu'il soit nécessaire de le transformer en singleton.

Il est important de noter que la plupart des principes SOLID ne s'appliquent pas nécessairement à ce stade particulier du développement de votre application. Par exemple, si vous devez diviser l'accès aux données en fonction de la complexité de celui-ci et si vous souhaitez tester la logique de votre référentiel sans toucher à la base de données. Cela semble peu probable (malheureusement, à mon avis), ce n'est donc probablement pas nécessaire.

Après toutes ces considérations, nous constatons que YAGNI et SOLID fournissent en réalité un conseil commun solide et immédiatement pertinent: il n’est probablement pas nécessaire de créer une interface de référentiel générique commune.

Toute cette réflexion est extrêmement utile comme exercice d'apprentissage. Vous prenez beaucoup de temps à apprendre, mais avec le temps, vous développez votre intuition et devenez très rapide. Vous saurez ce qu'il faut faire, mais vous n'avez pas besoin de penser à tous ces mots à moins que quelqu'un vous demande d'expliquer pourquoi.

Chris Ammerman
la source
Je pense que l'essentiel de la discussion sur cette page laisse de côté deux grands prétendants "couplage faible" et "cohésion forte" des principes GRAS. Les décisions de conception plus coûteuses découlent du principe de "faible couplage". Comme quand "activer" SRP + ISP + DIP pour un couplage faible. Exemple: une classe -> 3 classes dans un modèle MVC. Ou encore plus cher: diviser en .dll / .so modules / assemblies. C'est très coûteux en raison d'implications de construction, de projets, de listes de produits, de serveurs de construction, d'ajouts de fichiers versionnés à la source ...
v.oddou
6

Vous semblez croire que «bon design» signifie suivre une sorte d'idéologie et un ensemble de règles formelles qui doivent toujours être appliquées, même lorsqu'elles sont inutiles.

OMI c'est mauvais design. YAGNI est un composant d'un bon design, jamais une contradiction.

Vecteur
la source
2

Dans votre exemple, je dirais que YAGNI devrait prévaloir. Cela ne vous coûtera pas très cher si vous devez ajouter des interfaces ultérieurement. À propos, est-ce vraiment bien de concevoir une interface par classe si elle ne sert aucun objectif?

Une dernière pensée, peut-être que parfois, ce dont vous avez besoin n’est pas une bonne conception mais une conception suffisante. Voici une séquence très intéressante d'articles sur le sujet:

David
la source
Vos premier et troisième liens vont au même endroit.
David Thornley
2

Certaines personnes soutiennent que les noms d'interface ne devraient pas commencer par I. Plus précisément, l'une des raisons est que vous perdez en fait la dépendance selon que le type donné est une classe ou une interface.

Qu'est-ce qui vous interdit d' CustomerFactoryêtre une classe au début et ensuite de la changer en une interface, qui sera implémentée par DefaultCustormerFactoryou UberMegaHappyCustomerPowerFactory3000? La seule chose que vous devriez avoir à changer est l'endroit où l'implémentation est instanciée. Et si vous avez un design moins bon, il ne reste que quelques endroits au maximum.

La refactorisation fait partie du développement. Mieux vaut avoir peu de code, facile à refactoriser, que d’avoir une interface et une classe déclarées pour chaque classe, ce qui vous oblige à modifier chaque nom de méthode à au moins deux endroits à la fois.

Le principal intérêt des interfaces est la modularité, qui est peut-être le pilier le plus important d’une bonne conception. Notez cependant qu'un module n'est pas seulement défini par son découplage du monde extérieur (même si c'est ainsi que nous le percevons de la perspective extérieure), mais également par son fonctionnement interne.
Ce que je veux dire, c’est que découpler des choses, qui appartiennent intrinsèquement ensemble, n’a pas beaucoup de sens. D'une certaine manière, c'est comme si vous disposiez d'une armoire avec une étagère individuelle par tasse.

L'important est de concevoir un gros problème complexe en sous-problèmes plus petits et plus simples. Et vous devez vous arrêter au point où ils deviennent assez simples sans subdivisions supplémentaires, sinon ils deviendront en réalité plus compliqués. Cela pourrait être considéré comme un corollaire de YAGNI. Et cela signifie certainement un bon design.

L'objectif n'est pas de résoudre en quelque sorte un problème local avec un seul référentiel et une seule usine. Le but est que cette décision n’affecte pas le reste de votre demande. C'est ce que la modularité est à propos.
Vous voulez que vos collègues regardent votre module, voient une façade avec une poignée d'appels explicites et se sentent confiants de pouvoir les utiliser, sans avoir à se soucier de tous les branchements intérieurs potentiellement sophistiqués.

back2dos
la source
0

Vous créez interfaceplusieurs implémentations anticipant dans le futur. Vous pourriez aussi bien avoir un I<class>pour chaque classe dans votre base de code. Ne pas

Utilisez simplement la classe concrète unique selon YAGNI. Si vous constatez que vous devez disposer d'un objet "fictif" à des fins de test, transformez la classe d'origine en une classe abstraite avec deux implémentations, une avec la classe concrète d'origine et l'autre avec l'implémentation fictive.

Vous devrez évidemment mettre à jour toutes les instanciations de la classe d'origine pour instancier la nouvelle classe concrète. Vous pouvez contourner ce problème en utilisant un constructeur statique dès le départ.

YAGNI dit de ne pas écrire de code avant qu'il ne soit écrit.

Un bon design dit d'utiliser l'abstraction.

Vous pouvez avoir les deux. Une classe est une abstraction.

Jesse
la source
0

Pourquoi les interfaces de marqueur? Il me semble que cela ne fait rien d’autre que le "marquage". Avec un "tag" différent pour chaque type d'usine, à quoi ça sert?

Le but d'une interface est de donner à une classe "agit comme" un comportement, de leur donner une "capacité de référentiel" pour ainsi dire. Donc, si tous vos types de référentiels concrets se comportent comme le même IRepository (ils implémentent tous IRepository), ils peuvent tous être traités de la même manière par un autre code, avec le même code exact. À ce stade, votre conception est extensible. Ajout de types de référentiels plus concrets, tous traités comme des référentiels IR génériques - le même code traite tous les types concrets en tant que référentiels "génériques".

Les interfaces servent à manipuler des objets en se basant sur des points communs. Mais les interfaces de marqueur personnalisé a) n’ajoutent aucun comportement. et b) vous obliger à gérer leur unicité.

Dans la mesure où vous concevez des interfaces utiles, vous obtenez l'avantage de ne pas avoir à écrire de code spécialisé pour gérer des classes, des types ou des interfaces de marqueur personnalisées ayant une corrélation 1: 1 avec des classes concrètes. C'est une redondance inutile.

Je peux voir une interface de marqueur si, par exemple, vous aviez besoin d'une collection fortement typée de beaucoup de classes différentes. Dans la collection, ils sont tous des "ImarkerInterface", mais lorsque vous les sortez, vous devez les attribuer à leur type.

radarbob
la source
0

Pouvez-vous, maintenant, écrire un ICustomerRepository vaguement raisonnable? Par exemple, (atteignant vraiment ici, probablement un mauvais exemple), dans le passé, vos clients utilisaient-ils toujours PayPal? Ou tout le monde dans l'entreprise est brouillé au sujet de la connexion avec Alibaba? Si c'est le cas, vous voudrez peut-être utiliser la conception plus complexe maintenant et paraître clairvoyant pour vos patrons. :-)

Sinon, attendez. Deviner à une interface avant d'avoir une ou deux implémentations réelles échoue généralement. En d’autres termes, ne généralisez pas / abstenez-vous / utilisez un modèle de design fantaisie avant d’avoir quelques exemples à généraliser.

utilisateur949300
la source