Y a-t-il des inconvénients importants à dépendre des abstractions?

9

Je lisais ce wiki sur le principe des abstractions stables (SAP) .

Le SAP déclare que plus un package est stable, plus il doit être abstrait. Cela implique que si un paquet est moins stable (plus susceptible de changer), il devrait être plus concret. Ce que je ne comprends pas vraiment, c'est pourquoi cela devrait être le cas. Sûrement dans tous les cas, quelle que soit la stabilité, nous devrions dépendre d'abstractions et cacher la mise en œuvre concrète?

SteveCallender
la source
Essayez d'utiliser un composant avec lequel vous êtes à l'aise, en n'utilisant pas les abstractions qu'il fournit, mais en faisant tout en détail à un niveau inférieur à celui auquel vous êtes habitué. Cela vous donnera une assez bonne impression des avantages et des inconvénients de l'abstraction.
Kilian Foth,
2
Avez-vous lu l'article lié et / ou le livre sur lequel l'article est basé?
Jörg W Mittag
1
+1 Bonne question, d'autant plus que je ne pense pas que la relation entre stabilité et abstraction soit immédiatement intuitive. La page 11 de cet article aide, son exemple du cas abstrait a du sens, mais peut-être que quelqu'un peut écrire un exemple plus clair du cas concret. Demande de décollage.
Mike
Quel domaine problématique traitez-vous pour ces abstractions? Comme indiqué sur C2: "Dans la modélisation de domaines du monde réel - le monde des clients, des employés, des factures, des nomenclatures, des produits, des références, des chèques de paie, etc. - des abstractions stables peuvent être difficiles à trouver. Domaines informatiques - les le monde des piles, des files d'attente, des fonctions, des arbres, des processus, des threads, des widgets graphiques, des rapports, des formulaires, etc. - sont beaucoup plus susceptibles d'être stables. " et "Dans certains domaines, les abstractions stables sont difficiles à trouver." Sans savoir quel problème vous essayez de résoudre avec SAP, il est difficile de vous donner une bonne réponse.
@ JörgWMittag et Mike - Oui, j'ai lu l'article. Je pense simplement qu'il manque une explication pour expliquer pourquoi "les paquets instables devraient être concrets". À la page 13 dudit article, il montre un graphique mais n'explique pas trop en détail pourquoi (1,1) sur le graphique devrait être évité? Est-ce l'idée que fondamentalement instable signifie des dépendances moins afférentes et qu'il n'y a pas besoin d'utiliser l'abstraction? Si c'est le cas ... n'est-ce pas une bonne pratique d'utiliser l'abstraction de toute façon, juste au cas où la stabilité changerait avec les changements d'exigences ..
SteveCallender

Réponses:

7

Considérez vos packages comme une API, pour prendre l'exemple du papier, prenez les définitions de Readeravec string Reader.Read()et Writeravec void Writer.Write(string)comme votre API abstraite.

Vous pouvez ensuite créer une classe Copyavec une méthode Copier.Copy(Reader, Writer)et la mise en œuvre Writer.Write(Reader.Read())et peut - être certains contrôles de santé mentale.

Maintenant, vous faites des implémentations concrètes, par exemple FileReader, FileWriter, KeyboardReaderet DownloadThingsFromTheInternetReader.

Et si vous voulez changer votre implémentation de FileReader? Pas de problème, changez simplement la classe et recompilez.

Et si vous voulez changer la définition de votre abstraction Reader? Oops, vous ne pouvez pas changer cela, mais vous devrez aussi changer Copier, FileReader, KeyboardReaderet DownloadThingsFromTheInternetReader.

C'est la raison d'être du principe d'abstraction stable: rendez vos concrétisations moins stables que les abstractions.

Résidu
la source
1
Je suis d'accord avec tout ce que vous dites, mais je pense que la définition de stabilité des auteurs et la vôtre diffèrent. Vous traitez la stabilité comme un besoin de changer, selon l'auteur: «la stabilité n'est pas une mesure de la probabilité qu'un module change, mais plutôt une mesure de la difficulté de changer un module». Donc, ma question est plus pourquoi est-il avantageux pour les packages qui sont facilement modifiés d'être plus concrets plutôt qu'abstraits?
SteveCallender
1
@SteveCallender C'est une différence subtile: la définition de l'auteur de la "stabilité" est ce que la plupart des gens appellent le "besoin de stabilité", c'est-à-dire que plus les modules dépendent d'un module, plus le module doit être "stable".
Residuum
6

À cause de YAGNI .

Si vous n'avez actuellement qu'une seule implémentation d'une chose , pourquoi vous embêter avec une couche supplémentaire et inutile? Cela ne conduira qu'à une complexité inutile. Pire encore, vous fournissez parfois une abstraction en pensant au jour où une deuxième implémentation viendra ... et ce jour ne se produit jamais. Quelle perte de travail!

Je pense aussi que la vraie question à se poser n'est pas "Dois-je dépendre des abstractions?" mais plutôt "Ai-je besoin de modularité?". Et la modularité n'est pas toujours nécessaire, voir ci-dessous.

Dans l'entreprise dans laquelle je travaille, certains des logiciels que je développe sont fortement liés à un périphérique matériel avec lequel il doit communiquer. Ces appareils sont développés pour atteindre des objectifs très spécifiques et sont tout sauf modulaires. :-) Une fois que le premier produit appareil sort de l'usine et est installé quelque part, à la fois son firmware et le matériel ne peut jamais changer, jamais .

Je peux donc être sûr que certaines parties du logiciel n'évolueront jamais. Ces parties n'ont pas besoin de dépendre d'abstractions car il n'existe qu'une seule implémentation et celle-ci ne changera jamais. Déclarer des abstractions sur ces parties de code ne fera que dérouter tout le monde et prendra plus de temps (sans produire de valeur).

Pointé
la source
1
J'ai tendance à être d'accord avec YAGNI, mais je m'interroge sur votre exemple. Ne répétez-vous jamais de code dans les différents appareils? J'ai du mal à croire qu'il n'y a pas de code commun entre les appareils de la même entreprise. De plus, comment les clients aiment-ils lorsque vous ne corrigez pas les bogues dans leur micrologiciel? Voulez-vous dire qu'il n'y a jamais de bogues, jamais ? Si vous avez le même code qui est bogué dans 4 implémentations différentes, vous devez corriger le bogue 4 fois s'il n'est pas dans un module commun.
Fuhrmanator
1
Le code commun @Fuhrmanator est différent des abstractions. Le code commun peut simplement signifier une méthode ou une bibliothèque d'aide - aucune abstraction n'est nécessaire.
Eilon
@Fuhrmanator Bien sûr, nous avons du code commun dans les bibliothèques, mais comme l'a dit Eilon, tout ne dépend pas des abstractions (certaines parties le font cependant). Je n'ai jamais dit qu'il n'y avait jamais de bugs, j'ai dit qu'ils ne pouvaient pas être corrigés (pour des raisons qui sortent du cadre de la question du PO).
Repéré
@Eilon mon commentaire portait sur la modularité qui n'est pas toujours nécessaire (pas les abstractions).
Fuhrmanator
@Spotted Pas de problème pour ne pas pouvoir patcher. C'est juste un exemple assez spécifique et pas typique de la plupart des logiciels.
Fuhrmanator
6

Je pense que vous êtes peut-être dérouté par le mot stable choisi par Robert Martin. Voici où je pense que la confusion commence:

Cela implique que si un paquet est moins stable (plus susceptible de changer), il devrait être plus concret.

Si vous lisez l' article original , vous verrez (c'est moi qui souligne):

La définition classique du mot stabilité est: "Pas facile à déplacer." C'est la définition que nous utiliserons dans cet article. Autrement dit, la stabilité n'est pas une mesure de la probabilité qu'un module change; c'est plutôt une mesure de la difficulté de changer un module .

De toute évidence, les modules qui sont plus difficiles à changer vont être moins volatils. Plus le module est difficile à changer, c'est-à-dire plus il est stable, moins il sera volatil.

J'ai toujours eu du mal avec le choix de l'auteur du mot stable , car j'ai (comme vous) tendance à penser à l'aspect "vraisemblance" de la stabilité, c'est-à-dire peu susceptible de changer . La difficulté implique que le changement de ce module cassera beaucoup d'autres modules, et cela va demander beaucoup de travail pour corriger le code.

Martin utilise également les mots indépendant et responsable , qui me donnent beaucoup plus de sens. Dans son séminaire de formation, il a utilisé une métaphore sur les parents d'enfants qui grandissent et sur la façon dont ils devraient être «responsables», car leurs enfants dépendent d'eux. Le divorce, le chômage, l'incarcération, etc. sont de grands exemples concrets de l'impact négatif que le changement de parents aura sur les enfants. Par conséquent, les parents doivent être «stables» au profit de leurs enfants. Soit dit en passant, cette métaphore des enfants / parents n'est pas nécessairement liée à l'héritage dans la POO!

Donc, suivant l'esprit de «responsable», j'ai trouvé des significations alternatives pour difficile à changer (ou ne devrait pas changer ):

  • Obligatoire - ce qui signifie que les autres classes dépendent de cette classe, elle ne devrait donc pas changer.
  • Voici: ibid.
  • Contraint - les obligations de cette classe limitent sa facilité de changement.

Donc, brancher ces définitions dans la déclaration

plus un paquet est stable , plus il doit être abstrait

  • plus un paquet est contraint , plus il doit être abstrait
  • plus un paquet est redevable , plus il doit être abstrait
  • plus un paquet est contraint , plus il doit être abstrait

Citons le principe des abstractions stables (SAP), en insistant sur les mots déroutants stable / instable:

Les packages qui sont au maximum stables doivent être au maximum abstraits. Les colis instables doivent être concrets. L'abstraction d'un paquet doit être proportionnelle à sa stabilité .

Clarifier sans ces mots déroutants:

Les packages qui sont au maximum redevables à d'autres parties du système doivent être au maximum abstraits. Les packages qui peuvent changer sans difficulté doivent être concrets. L'abstraction d'un paquet doit être proportionnelle à la difficulté de modification .

TL; DR

Le titre de votre question demande:

Y a-t-il des inconvénients importants à dépendre des abstractions?

Je pense que si vous créez correctement les abstractions (par exemple, elles existent parce que beaucoup de code en dépend), alors il n'y a pas d'inconvénients importants.

Fuhrmanator
la source
0

Cela implique que si un paquet est moins stable (plus susceptible de changer), il devrait être plus concret. Ce que je ne comprends pas vraiment, c'est pourquoi cela devrait être le cas.

Les abstractions sont des choses difficiles à changer dans le logiciel car tout dépend d'elles. Si votre paquet va changer souvent et qu'il fournit des abstractions, les personnes qui en dépendent seront obligées de réécrire une grande partie de leur code lorsque vous changez quelque chose. Mais si votre package instable fournit des implémentations concrètes, beaucoup moins de code devra être réécrit après les modifications.

Donc, si votre paquet va changer souvent, il devrait mieux fournir des bétons, pas des abstractions. Sinon ... qui diable l'utilisera? ;)

Vladislav Rastrusny
la source
0

Gardez à l'esprit la métrique de stabilité de Martin et ce qu'il entend par «stabilité»:

Instability = Ce / (Ca+Ce)

Ou:

Instability = Outgoing / (Incoming+Outgoing)

Autrement dit, un package est considéré comme complètement instable si toutes ses dépendances sont sortantes: il utilise d'autres choses, mais rien ne l'utilise. Dans ce cas, il est logique que cette chose soit concrète. Ce sera également le type de code le plus facile à modifier car rien d'autre ne l'utilise, et donc rien d'autre ne peut se casser si ce code est modifié.

Pendant ce temps, lorsque vous avez le scénario opposé de "stabilité" complète avec un package utilisé par une ou plusieurs choses, mais qu'il n'utilise rien seul, comme un package central utilisé par le logiciel, c'est à ce moment que Martin dit que cette chose devrait être abstrait. Cela est également renforcé par la partie DIP de SOLI (D), le principe d'inversion des dépendances, qui stipule essentiellement que les dépendances doivent circuler uniformément vers les abstractions pour le code de bas et de haut niveau.

C'est-à-dire que les dépendances devraient se diriger uniformément vers la "stabilité", et plus précisément, les dépendances devraient se diriger vers les packages avec plus de dépendances entrantes que les dépendances sortantes et, en outre, les dépendances devraient se diriger vers les abstractions. L'essentiel de la raison derrière cela est que les abstractions offrent une marge de manœuvre pour substituer un sous-type à un autre, offrant ce degré de flexibilité pour que les parties concrètes implémentant l'interface changent sans casser les dépendances entrantes à cette interface abstraite.

Y a-t-il des inconvénients importants à dépendre des abstractions?

Eh bien, je suis en fait en désaccord avec Martin ici pour mon domaine au moins, et ici je dois introduire une nouvelle définition de "stabilité" comme dans "manque de raisons de changer". Dans ce cas, je dirais que les dépendances devraient se diriger vers la stabilité, mais les interfaces abstraites n'aident pas si les interfaces abstraites sont instables (par ma définition de "instable", comme sujettes à être modifiées à plusieurs reprises, pas Martin). Si les développeurs ne parviennent pas à obtenir les abstractions correctes et que les clients changent d'avis à plusieurs reprises de manière à rendre les tentatives abstraites de modélisation du logiciel incomplètes ou inefficaces, alors nous ne bénéficions plus de la flexibilité améliorée des interfaces abstraites pour protéger le système contre les changements en cascade qui brisent les dépendances . Dans mon cas personnel, j'ai trouvé des moteurs ECS, tels que ceux trouvés dans les jeux AAA,le plus concret : vers les données brutes, mais ces données sont très stables (comme dans "peu susceptibles de devoir être modifiées"). J'ai souvent trouvé la probabilité que quelque chose nécessitant des changements futurs soit une mesure plus utile que le rapport des couplages efférents au total pour guider les décisions SE.

Donc, je modifierais un peu DIP et je dirais simplement que "les dépendances devraient se diriger vers les composants qui ont la plus faible probabilité de nécessiter d'autres modifications", que ces composants soient des interfaces abstraites ou des données brutes. Tout ce qui m'importe, c'est la probabilité qu'elles nécessitent des changements directs de conception. Les abstractions ne sont utiles dans ce contexte de stabilité que si quelque chose, en étant abstrait, réduit cette probabilité.

Dans de nombreux contextes, ce pourrait être le cas avec des ingénieurs et des clients décents qui anticipent les besoins du logiciel à l'avance et conçoivent des abstractions stables (comme dans, immuables), tandis que ces abstractions leur offrent toute la marge de manœuvre dont elles ont besoin pour échanger des implémentations concrètes. Mais dans certains domaines, les abstractions peuvent être instables et sujettes à être inadéquates, tandis que les données requises du moteur peuvent être beaucoup plus faciles à anticiper et à stabiliser à l'avance. Dans ces cas, il peut donc être plus avantageux du point de vue de la maintenabilité (la facilité de changer et d'étendre le système) que les dépendances se dirigent vers les données plutôt que vers les abstractions. Dans un ECS, les parties les plus instables (comme dans les pièces les plus fréquemment modifiées) sont généralement les fonctionnalités résidant dans les systèmes (PhysicsSystem, par exemple), tandis que les parties les plus stables (comme celles qui sont le moins susceptibles d'être modifiées) sont les composants qui ne sont constitués que de données brutes ( MotionComponent, par exemple) que tous les systèmes utilisent.


la source