Suivre SOLID amène-t-il à écrire un cadre au-dessus de la pile technologique?

70

J'aime SOLID et je fais de mon mieux pour l’utiliser et l’appliquer lorsque je me développe. Mais je ne peux pas m'empêcher de penser que l'approche SOLID transforme votre code en code 'framework' - le code que vous concevriez si vous créiez un framework ou une bibliothèque que d'autres développeurs pourraient utiliser.

J'ai généralement pratiqué 2 modes de programmation: créer plus ou moins exactement ce qui est demandé via les exigences et KISS (programmation typique), ou créer une logique, des services, etc. très génériques et réutilisables offrant la flexibilité dont d'autres développeurs peuvent avoir besoin (programmation par cadre). .

Si l'utilisateur veut vraiment juste qu'une application fasse des choses x et y, est-il logique de suivre SOLID et d'ajouter toute une série de points d'entrée d'abstraction, alors que vous ne savez même pas si c'est un problème valable pour commencer avec? Si vous ajoutez ces points d’abstraction d’entrée, répondez-vous réellement aux besoins des utilisateurs ou créez-vous un cadre au-dessus de votre cadre existant et de votre pile technologique pour faciliter les ajouts futurs? Dans quel cas servez-vous les intérêts du client ou du développeur?

C'est quelque chose qui semble commun dans le monde de Java Enterprise, où vous avez l'impression de concevoir votre propre framework au-dessus de J2EE ou de Spring pour qu'il soit un meilleur UX pour le développeur, au lieu de vous concentrer sur l'UX pour l'utilisateur?

Igneous01
la source
12
Le problème avec la plupart des règles empiriques de programmation brèves est qu’elles sont sujettes à interprétation, à des cas extrêmes et que, parfois, la définition des mots dans ces règles n’est pas claire lorsqu’on examine de plus près. Ils peuvent essentiellement signifier une grande variété de choses pour différentes personnes. Avoir un pragmatisme non idéologique permet généralement de prendre des décisions plus sages.
Mark Rogers
1
Cela donne l'impression que suivre les principes SOLID implique en quelque sorte un gros investissement, beaucoup de travail supplémentaire. Ce n'est pas, c'est pratiquement gratuit. Et cela vous épargnera probablement, à vous ou à quelqu'un d'autre, un gros investissement dans le futur, car cela facilitera la maintenance et l'extension de votre code. Vous continuez à poser des questions telles que "devons-nous faire nos devoirs ou rendre le client heureux?" Ce ne sont pas des compromis.
Martin Maat
1
@MartinMaat Je pense que les formes les plus extrêmes de SOLID impliquent de gros investissements. C'est à dire. Logiciels d'entreprise. En dehors des logiciels d'entreprise, vous auriez très peu de raisons de résumer votre ORM, votre pile technologique ou votre base de données car il y a de fortes chances pour que vous conserviez votre pile choisie. Dans le même sens, en vous attachant à un cadre, une base de données ou un ORM particulier, vous enfreignez les principes SOLID car vous êtes couplé à votre pile. Ce niveau de flexibilité de SOLID n’est pas requis dans la plupart des emplois.
Igneous01
1
Voir aussi l' effet de plate-forme interne .
Maxpm
1
Transformer la plupart du code en quelque chose comme un framework ne semble pas terrible. Cela ne devient terrible que si cela devient trop technique. Mais les cadres peuvent être minimes et avoir une opinion. Je ne suis pas sûr que ce soit une conséquence inévitable de la suite de SOLID, mais c’est certainement une conséquence possible et que vous devriez embrasser, je pense.
Konrad Rudolph

Réponses:

84

Votre observation est correcte, les principes SOLID sont conçus à mon humble avis avec des bibliothèques réutilisables ou un code de framework à l’esprit. Lorsque vous les suivez tous aveuglément, sans vous demander si cela a un sens ou non, vous risquez de trop généraliser et d'investir beaucoup plus d'efforts dans votre système que probablement nécessaire.

C'est un compromis, et il faut un peu d'expérience pour prendre les bonnes décisions quant au moment opportun pour généraliser ou non. Une approche possible consiste à s'en tenir au principe de YAGNI - ne rendez pas votre code SOLID "au cas où" - ou, pour reprendre vos mots: ne le faites pas.

fournir la flexibilité dont d'autres développeurs peuvent avoir besoin

au lieu de cela, fournissez la flexibilité dont les autres développeurs ont réellement besoin dès qu'ils en ont besoin , mais pas avant.

Ainsi, chaque fois que vous avez une fonction ou une classe dans votre code, vous ne savez pas si elle pourra être réutilisée, ne la placez pas dans votre cadre pour le moment. Attendez que vous ayez un cas réel de réutilisation et que le refactor devienne "assez solide pour ce cas". N'implémentez pas davantage de configurabilité (en suivant l'OCP), ni de points d'abstraction d'entrée (à l'aide du DIP) dans une classe dont vous avez réellement besoin pour le cas de réutilisation réel. Ajoutez la flexibilité suivante lorsque la prochaine exigence de réutilisation est réellement présente.

Bien sûr, cette façon de travailler nécessitera toujours une refonte de la base de code existante. C'est pourquoi les tests automatiques sont importants ici. Donc, rendre votre code suffisamment SOLIDE dès le début pour qu'il soit testable par unité n'est pas une perte de temps, et cela ne contredit pas YAGNI. Les tests automatiques constituent un cas valable de "réutilisation de code", car le code en jeu est utilisé à partir du code de production ainsi que des tests. Mais gardez à l'esprit, ajoutez simplement la flexibilité dont vous avez réellement besoin pour que les tests fonctionnent, ni moins, ni plus.

C'est en fait une vieille sagesse. Il y a longtemps avant que le terme SOLID devenu populaire, quelqu'un m'a dit avant d' essayer d'écrire à nouveau le code utilisable, nous devrions écrire utilisables code. Et je pense toujours que c'est une bonne recommandation.

Doc Brown
la source
23
Points de contention supplémentaires: attendez que vous ayez la même logique dans 3 cas d'utilisation avant de refactoriser votre code pour le réutiliser. Si vous commencez à refactoriser avec 2 pièces, il est facile de vous retrouver dans une situation où des exigences changeantes ou un nouveau cas d'utilisation finissent par casser votre abstraction. Limitez également les refactors aux objets ayant le même cas d'utilisation: 2 composants peuvent avoir le même code mais faire des choses complètement différentes. Si vous fusionnez ces composants, vous liez cette logique, ce qui peut poser des problèmes plus tard.
Nzall
8
Je suis généralement d’accord avec cela, mais j’ai l’impression qu’il est trop centré sur les applications «ponctuelles»: vous écrivez le code, cela fonctionne, d’accord. Cependant, il y a beaucoup d'applications avec "support de longue date". Vous pourriez écrire du code et, deux ans plus tard, les exigences commerciales changent et vous devez donc ajuster le code. À ce moment-là, beaucoup d'autres codes pourraient en dépendre - dans ce cas, les principes SOLID faciliteront les changements.
R. Schmitz
3
"Avant d'essayer d'écrire du code réutilisable, nous devrions écrire du code utilisable" - Très sage!
Graham
10
Il est probablement intéressant de noter que l' attente jusqu'à ce que vous avez un cas d' utilisation réelle fera votre code SOLID mieux , car travailler dans hypotheticals est très difficile et vous êtes susceptible de mal deviner ce que les besoins futurs seront. Notre projet a un certain nombre de cas où les choses ont été conçues pour être robuste et flexible pour les besoins futurs ... sauf les besoins futurs avéré être les choses ne pensait à l'époque, nous avons donc à la fois nécessaire pour factoriser et avait une flexibilité supplémentaire nous toujours pas besoin - qui soit devait être maintenu face à la refactorisation, ou mis au rebut.
KRyan
2
de plus, vous aurez toujours besoin d'écrire du code testable, ce qui signifie généralement d'avoir une première couche d'abstraction afin de pouvoir passer d'une implémentation concrète à une implémentation testée.
Walfrat
49

D'après mon expérience, lorsque vous écrivez une application, vous avez trois choix:

  1. Écrire du code uniquement pour répondre aux exigences,
  2. Rédiger un code générique qui anticipe les besoins futurs, tout en satisfaisant aux exigences actuelles,
  3. Écrivez un code qui répond uniquement aux exigences actuelles, mais d'une manière facile à modifier ultérieurement pour répondre à d'autres besoins.

Dans le premier cas, il est courant de se retrouver avec un code étroitement couplé qui manque de tests unitaires. Bien sûr, c'est rapide à écrire, mais difficile à tester. Et c'est une douleur royale juste de changer plus tard lorsque les exigences changent.

Dans le second cas, on passe beaucoup de temps à essayer d’anticiper les besoins futurs. Et trop souvent, ces exigences futures ne se matérialisent jamais. Cela semble le scénario que vous décrivez. C'est un gaspillage d'efforts la plupart du temps et donne lieu à un code inutilement complexe qu'il est encore difficile de modifier lorsqu'une exigence inattendue se présente.

Le dernier cas est celui à viser à mon avis. Utilisez TDD ou des techniques similaires pour tester le code au fur et à mesure et vous obtiendrez un code faiblement couplé, facile à modifier mais rapide à écrire. Et le fait est que, ce faisant, vous suivez naturellement bon nombre des principes SOLID: petites classes et fonctions; interfaces et dépendances injectées. Et Mme Liskov est généralement satisfaite aussi, car les classes simples à responsabilité unique ne sont pas gênées par son principe de substitution.

Le seul aspect de SOLID qui ne s'applique pas vraiment ici est le principe d'ouverture / fermeture. Pour les bibliothèques et les frameworks, c'est important. Pour une application autonome, pas tellement. En réalité, c’est un cas d’écriture de code qui suit " SLID ": facile à écrire (et à lire), facile à tester et facile à maintenir.

David Arno
la source
une de mes réponses préférées sur ce site!
TheCatWhisperer
Je ne sais pas comment vous concluez que 1) est plus difficile à tester que 3). Plus difficile de faire des changements, bien sûr, mais pourquoi ne pas tester? Au contraire, il est plus facile de tester un logiciel déterminé que par un logiciel plus général.
M. Lister
@MrLister Les deux vont de pair, 1. est plus difficile à tester que 3. parce que la définition implique qu'elle n'est pas écrite "de manière à pouvoir être modifié ultérieurement pour répondre à d'autres besoins".
Mark Booth le
1
+0; IMVHO vous interprétez mal (bien que de manière commune) la façon dont fonctionne 'O' (ouvert-fermé). Voir par exemple codeblog.jonskeet.fr/2013/03/15/… - même dans de petites bases de code, il est plus important de disposer d'unités de code autonomes (par exemple, des classes, des modules, des packages, etc.), qui peuvent être testées isolément et ajoutées / enlevé au besoin. Un exemple de ce type serait un ensemble de méthodes utilitaires - quelle que soit la manière dont vous les regroupez, elles doivent être "fermées", c'est-à-dire qu'elles doivent être autonomes et "ouvertes", c'est-à-dire qu'elles peuvent être étendues d'une manière ou d'une autre.
Vaxquis
BTW, même oncle Bob, va même dans ce sens à un moment donné: «Ce que [open-closed] signifie, c’est que vous devriez vous efforcer d’obtenir votre code dans une position telle que, lorsque le comportement change de la manière attendue, vous n’aurez pas à vous balayer. modifications à tous les modules du système. Idéalement, vous pourrez ajouter le nouveau comportement en ajoutant du nouveau code et en modifiant peu ou pas l'ancien. " <Ceci s'applique évidemment même aux petites applications, si elles sont destinées à être modifiées ou corrigées (et, IMVHO, qui est généralement le cas, surtout quand au sujet des corrections. ricanent )
vaxquis
8

Votre perspective peut être faussée par votre expérience personnelle. Il s’agit d’une pente glissante de faits individuellement corrects, mais la déduction qui en résulte ne l’est pas, même si elle semble correcte au premier abord.

  • Les cadres ont une portée plus grande que les petits projets.
  • Les mauvaises pratiques sont beaucoup plus difficiles à gérer dans les bases de code plus grandes.
  • Construire un framework (en moyenne) nécessite un développeur plus habile que de construire un petit projet.
  • Les meilleurs développeurs suivent davantage les bonnes pratiques (SOLID).
  • En conséquence, les cadres ont davantage besoin de bonnes pratiques et ont tendance à être construits par des développeurs plus expérimentés.

Cela signifie que lorsque vous interagissez avec des frameworks et des bibliothèques plus petites, le code de bonne pratique avec lequel vous interagissez se trouve plus souvent dans les frameworks plus grands.

Cette erreur est très courante, par exemple chaque médecin chez qui j'ai été traité était arrogant. Par conséquent, je conclus que tous les médecins sont arrogants. Ces sophismes souffrent toujours de faire une inférence générale basée sur des expériences personnelles.

Dans votre cas, il est possible que vous ayez principalement expérimenté les bonnes pratiques dans des cadres plus grands et non dans des bibliothèques plus petites. Votre observation personnelle n’est pas fausse, mais il s’agit d’une preuve anecdotique et non universellement applicable.


2 modes de programmation - créer plus ou moins exactement ce qui est demandé via les exigences et KISS (programmation typique), ou créer une logique, des services, etc. très génériques et réutilisables offrant la flexibilité dont d'autres développeurs peuvent avoir besoin (programmation cadre)

Vous confirmez un peu cela ici. Pensez à ce qu'est un cadre. Ce n'est pas une application. C'est un "modèle" généralisé que d'autres peuvent utiliser pour faire toutes sortes d'applications. Logiquement, cela signifie qu'un cadre est construit dans une logique beaucoup plus abstraite pour pouvoir être utilisé par tout le monde.

Les constructeurs de framework sont incapables de prendre des raccourcis, car ils ne savent même pas quelles sont les exigences des applications suivantes. Construire un framework les incite naturellement à rendre leur code utilisable par d'autres.

Les concepteurs d'applications ont toutefois la possibilité de compromettre l'efficacité logique, car ils se concentrent sur la fourniture d'un produit. Leur principal objectif n'est pas le fonctionnement du code, mais plutôt l'expérience de l'utilisateur.

Pour un framework, l'utilisateur final est un autre développeur, qui interagira avec votre code. La qualité de votre code est importante pour l'utilisateur final.
Pour une application, l'utilisateur final est un non-développeur, qui n'interagira pas avec votre code. La qualité de votre code n’a aucune importance pour eux.

C'est précisément pourquoi les architectes d'une équipe de développement agissent souvent en tant que responsables de l'application des bonnes pratiques. La livraison du produit leur est très facile, ce qui signifie qu'ils ont tendance à regarder le code de manière objective, plutôt que de se concentrer sur la livraison de l'application elle-même.


Si vous ajoutez ces points d’abstraction d’entrée, répondez-vous réellement aux besoins des utilisateurs ou créez-vous un cadre au-dessus de votre cadre existant et de votre pile technologique pour faciliter les ajouts futurs? Dans quel cas servez-vous les intérêts du client ou du développeur?

C’est un point intéressant, et c’est (selon mon expérience) la principale raison pour laquelle les gens essaient encore de justifier d’éviter les bonnes pratiques.

Pour résumer les points ci-dessous: Ignorer les bonnes pratiques ne peut être justifié que si vos exigences (telles que connues à ce jour) sont immuables et qu’il n’y aura jamais de modification / ajout à la base de code. Alerte spoiler: C'est rarement le cas.
Par exemple, lorsque j'écris une application console 5 minutes pour traiter un fichier particulier, je n'utilise pas les bonnes pratiques. Parce que je vais seulement utiliser l'application aujourd'hui, et qu'elle n'aura pas besoin d'être mise à jour à l'avenir (il serait plus facile d'écrire une application différente si j'en avais besoin une de plus).

Supposons que vous puissiez compiler correctement une application en 4 semaines et que vous puissiez le créer correctement en 6 semaines. À première vue, la construction bâclée semble meilleure. Le client reçoit leur application plus rapidement et l'entreprise doit consacrer moins de temps aux salaires des développeurs. Gagner / Gagner, non?

Cependant, cette décision est prise sans réflexion. En raison de la qualité de la base de code, il faut compter 2 semaines pour apporter des modifications majeures à celui qui a été construit de façon médiocre, tandis qu’il faut une semaine pour les mêmes modifications. Beaucoup de ces changements pourraient se produire à l’avenir.

En outre, les modifications ont tendance à nécessiter de manière inattendue plus de travail que prévu au départ dans des bases de code mal construites, ce qui a pour effet de forcer votre temps de développement à trois semaines au lieu de deux.

Et puis, il y a aussi la tendance à perdre du temps à chasser les insectes. C'est souvent le cas dans les projets où la journalisation a été ignorée en raison de contraintes de temps ou de réticence totale à la mettre en œuvre car vous travaillez de manière distraite en supposant que le produit final fonctionnera comme prévu.

Cela n'a même pas besoin d'être une mise à jour majeure. Chez mon employeur actuel, j'ai vu plusieurs projets construits rapidement et mal, et lorsque le plus petit bogue / changement devait être corrigé en raison d'une mauvaise communication dans les exigences, cela a entraîné une réaction en chaîne du besoin de refactoriser module après module. . Certains de ces projets ont fini par s'effondrer (et laisser derrière eux un gâchis incommenable) avant même de publier leur première version.

Les décisions de raccourci (programmation rapide et imprécise) ne sont utiles que si vous pouvez garantir de manière concluante que les exigences sont exactement correctes et qu’elles n’auront jamais besoin de changer. D'après mon expérience, je n'ai jamais rencontré de projet où c'est vrai.

Investir plus de temps dans les bonnes pratiques, c'est investir dans l'avenir. Les bugs et modifications futurs seront tellement plus faciles lorsque la base de code existante est construite sur de bonnes pratiques. Il versera déjà des dividendes après seulement deux ou trois modifications.

Flater
la source
1
C'est une bonne réponse, mais je dois préciser que je ne dis pas que nous abandonnons les bonnes pratiques, mais quel niveau de «bonnes pratiques» poursuivons-nous? Est-ce une bonne pratique d'abréger votre ORM dans chaque projet parce que vous pourriez avoir besoin de l'échanger pour un autre plus tard? Je ne pense pas, il y a certains niveaux de couplage que je suis prêt à accepter (c'est-à-dire que je suis lié au cadre, à la langue, à l'ORM, à la base de données choisie). Si nous suivons SOLID à un degré extrême, mettons-nous vraiment en place notre propre cadre au-dessus de la pile choisie?
Igneous01
Vous niez l'expérience de OP comme une "erreur". Pas constructif.
max630
@ max630 Je ne le nie pas. J'ai passé une bonne partie de la réponse à expliquer pourquoi les observations d'OP sont valides.
Flater
1
@ Igneous01 SOLID n'est pas un framework. SOLID est une abstraction, plus courante dans un framework. Lorsque vous implémentez n'importe quel type d'abstraction (y compris SOLID), il existe toujours une ligne de raisonnabilité. Vous ne pouvez pas simplement faire abstraction pour abstraction, vous passeriez des siècles à créer un code tellement généralisé qu'il est difficile de suivre. Abstrait seulement ce que vous pensez raisonnablement vous être utile à l’avenir. Cependant, ne tombez pas dans le piège de supposer que vous êtes lié, par exemple, à votre serveur de base de données actuel. Vous ne savez jamais quelle nouvelle base de données sera publiée demain.
Flater
@ Igneous01 En d'autres termes, vous avez la bonne idée en ne voulant pas tout résumer, mais j'ai le sentiment que vous vous penchez un peu trop dans cette direction. Il est très courant que les développeurs supposent que les exigences actuelles sont bien définies et prennent ensuite des décisions architecturales basées sur cette hypothèse (pieux).
Flater
7

Comment SOLID transforme-t-il un code simple en code-cadre? Je ne suis certes pas un fan de SOLID, mais ce que vous voulez dire n’est pas vraiment évident.

  • Kiss est l'essence du S Principe Ingle de responsabilité.
  • Il n'y a rien dans le principe « O penché / fermé» (du moins si je comprends bien - voir Jon Skeet ) qui s'oppose à l'écriture de code pour bien faire une chose. (En fait, plus le code est étroitement ciblé, plus la partie «fermée» devient importante.)
  • Le principe de substitution de L iskov ne dit pas que vous devez laisser les gens sous-classer vos classes. Il est dit que si vous sous-classez vos classes, vos sous-classes doivent remplir le contrat de leurs super-classes. C'est juste une bonne conception OO. (Et si vous n'avez aucune sous-classe, cela ne s'applique pas.)
  • Kiss est aussi l'essence du I nterface Principe Ségrégation.
  • Le D ependency principe est le Inversion seul que je peux voir à distance l' application, mais je pense qu'il est très mal compris et surfaite. Cela ne signifie pas que vous devez tout injecter avec Guice ou Spring. Cela signifie simplement que vous devez résumer le cas échéant et ne pas dépendre des détails de la mise en œuvre.

J'avoue que je ne pense pas en termes solides moi-même, car je suis passé par les écoles de programmation Gang of Four et Josh Bloch , et non par l'école Bob Martin. Mais je pense vraiment que si vous pensez “SOLID” = “ajouter plus de couches à la pile de technologies”, vous la lisez mal.


PS Ne vendez pas les avantages de “meilleur UX pour le développeur”. Le code passe le plus clair de son temps en maintenance. Un développeur, c'est vous .

David Moles
la source
1
En ce qui concerne SRP - on pourrait soutenir que toute classe avec un constructeur viole SRP, car vous pouvez transférer cette responsabilité à une usine. En ce qui concerne OCP - il s’agit vraiment d’un problème au niveau de la structure, car une fois que vous publiez une interface destinée à une utilisation externe, vous ne pouvez pas la modifier. Si l'interface est uniquement utilisée dans votre projet, il est possible de modifier le contrat, car vous avez le pouvoir de modifier le contrat dans votre propre code. En ce qui concerne les FAI - on pourrait soutenir qu’une interface devrait être définie pour chaque action individuelle (préservant ainsi la SRP), et concerne les utilisateurs extérieurs.
Igneous01
3
1) on pourrait, mais je doute que quiconque mérite d’écouter l’ait jamais fait. 2) vous pourriez être surpris de la rapidité avec laquelle un projet peut atteindre une taille telle que modifier librement les interfaces internes devient une mauvaise idée. 3) voir à la fois 1) et 2). Autant dire que je pense que vous lisez trop dans les trois principes. Mais les commentaires ne sont pas vraiment l'endroit pour aborder ces arguments; Je vous suggère de poser chacune d'elles séparément et de voir quel type de réponses vous obtenez.
David Moles
4
@ Igneous01 En utilisant cette logique, vous pouvez aussi bien abandonner les accesseurs et les setters, car vous pouvez créer une classe séparée pour chaque setter de variable et une pour chaque getter. IE: class A{ int X; int Y; } class A_setX{ f(A a, int N) { a.X = N; }} class A_getX{ int f(A a) { return X; }} class A_setY ... etc.Je pense que vous envisagez cela d'un point de vue trop méta avec votre revendication d'usine. L'initialisation n'est pas un aspect du problème de domaine.
Aaron
@ Aaron This. Les gens peuvent utiliser SOLID pour faire de mauvais arguments, mais cela ne signifie pas faire de mauvaises choses = «suivre SOLID».
David Moles