Assurez-vous que chaque classe n'a qu'une responsabilité, pourquoi?

37

Selon la documentation de Microsoft, l'article de SOLID sur Wikipedia, ou la plupart des architectes informatiques, nous devons nous assurer que chaque classe n'a qu'une seule responsabilité. J'aimerais savoir pourquoi, parce que si tout le monde semble être d'accord avec cette règle, personne ne semble être d'accord sur les raisons de cette règle.

Certains citent une meilleure maintenance, d'autres disent que cela facilite les tests ou rend la classe plus robuste, ou plus sûre. Qu'est-ce qui est correct et qu'est-ce que cela signifie réellement? Pourquoi améliore-t-il la maintenance, facilite les tests ou renforce le code?

Bastien Vandamme
la source
1
J'ai eu une question que vous pourriez trouver liée aussi: Quelle est la vraie responsabilité d'une classe?
Pierre Arlaud
3
Toutes les raisons de la responsabilité unique ne peuvent-elles pas être correctes? Cela facilite la maintenance. Cela facilite les tests. Cela rend la classe plus robuste (en général).
Martin York
2
Pour la même raison, vous avez des cours du tout.
Davor Ždralo
2
J'ai toujours eu un problème avec cette déclaration. Il est très difficile de définir ce qu'est une "responsabilité unique". Une seule responsabilité peut aller de "vérifier que 1 + 1 = 2" à "conserver un journal précis de tous les fonds versés et sortis des comptes bancaires de l'entreprise".
Dave Nay

Réponses:

57

Modularité. N'importe quel langage décent vous donnera les moyens de coller des morceaux de code, mais il n'y a pas de moyen général de décoller un gros morceau de code sans que le programmeur effectue une opération à la source. En regroupant de nombreuses tâches dans une construction de code, vous vous privez, ainsi que d'autres personnes, de la possibilité de combiner ses éléments d'une autre manière, et vous introduisez des dépendances inutiles qui pourraient affecter les modifications apportées à un élément.

SRP est tout aussi applicable aux fonctions qu'aux classes, mais les langages POO traditionnels sont relativement peu aptes à coller des fonctions ensemble.

Doval
la source
12
+1 pour mentionner la programmation fonctionnelle comme moyen plus sûr de composer des abstractions.
logc
> mais les langues OOP traditionnelles sont relativement peu aptes à coller des fonctions ensemble. <Peut-être parce que les langues OOP ne sont pas concernées par les fonctions, mais par les messages.
Jeff Hubbard
@JeffHubbard Je pense que vous voulez dire que c'est parce qu'ils prennent après C / C ++. Rien dans les objets ne les rend mutuellement exclusifs avec des fonctions. Enfer, un objet / interface n'est qu'un enregistrement / struct de fonctions. Prétendre que les fonctions ne sont pas importantes est injustifié en théorie et en pratique. Ils ne peuvent pas être évités de toute façon - vous finissez par devoir lancer "Runnables", "Usines", "Fournisseurs" et "Actions" ou utilisez les "modèles" de stratégie et de méthode de modèle, et vous vous retrouvez avec un tas de passe-partout inutiles pour faire le même travail.
Doval
@Doval Non, je veux vraiment dire que la POO concerne les messages. Cela ne veut pas dire qu’une langue donnée est meilleure ou pire qu’un langage de programmation fonctionnel pour coller des fonctions ensemble, mais que ce n’est pas la préoccupation première de la langue .
Jeff Hubbard
Fondamentalement, si votre langage vise à rendre la POO plus facile / meilleure / plus rapide / plus forte, le fait de vous concentrer ou non sur l’intégration des fonctions n’est pas ce sur quoi vous vous concentrez.
Jeff Hubbard
30

Une meilleure maintenance, des tests faciles, une résolution plus rapide des bogues ne sont que des résultats (très agréables) de l'application de SRP. La principale raison (comme le dit Robert C. Matin) est:

Une classe devrait avoir une et une seule raison de changer.

En d'autres termes, SRP soulève la localité du changement .

SRP favorise également le code DRY. Tant que nous avons des classes qui n'ont qu'une seule responsabilité, nous pouvons choisir de les utiliser n'importe où. Si nous avons une classe qui a deux responsabilités, mais que nous n’avons besoin que d’une seule et que la seconde se mêle, nous avons 2 options:

  1. Copiez-collez la classe dans une autre et peut-être même créez-vous un autre mutant multi-responsabilité (soulevez un peu la dette technique).
  2. Divisez la classe et faites-la comme il se doit, ce qui peut coûter cher en raison de l'utilisation intensive de la classe d'origine.
Maciej Chałapuk
la source
1
+1 pour relier SRP à DRY.
Mahdi
21

Il est facile de créer du code pour résoudre un problème particulier. Il est plus compliqué de créer du code qui corrige ce problème tout en permettant d’apporter des modifications ultérieures en toute sécurité. SOLID fournit un ensemble de pratiques qui améliorent le code.

Quant à savoir lequel est correct: les trois. Ce sont tous des avantages à utiliser une responsabilité unique et la raison pour laquelle vous devriez l’utiliser.

En ce qui concerne ce qu'ils veulent dire:

  • Un meilleur entretien signifie qu'il est plus facile de changer et ne change pas aussi souvent. Comme il y a moins de code et que ce code est centré sur quelque chose de spécifique, si vous devez modifier quelque chose qui n'est pas lié à la classe, la classe n'a pas besoin d'être modifiée. De plus, lorsque vous devez changer de classe, tant que vous n'avez pas besoin de changer d'interface publique, vous n'avez qu'à vous soucier de cette classe et de rien d'autre.
  • Des tests faciles signifient moins de tests, moins de configuration. Il n'y a pas autant de pièces mobiles dans la classe; le nombre d'échecs possibles sur la classe est donc plus petit et, par conséquent, moins de cas à tester. Il y aura moins de champs privés / membres à configurer.
  • En raison des deux précédents, vous obtenez une classe qui change moins et échoue moins, et est donc plus robuste.

Essayez de créer du code pendant un certain temps en suivant le principe et revisitez ce code ultérieurement pour apporter des modifications. Vous verrez l’énorme avantage qu’elle procure.

Vous faites la même chose pour chaque classe et vous vous retrouvez avec plus de classes, toutes conformes à SRP. Cela rend la structure plus complexe du point de vue des connexions, mais la simplicité de chaque classe le justifie.

Miyamoto Akira
la source
Je ne pense pas que les points soulevés ici sont justifiés. En particulier, comment éviter les jeux à somme nulle où la simplification d'une classe rend les autres classes plus complexes, sans effet net sur la base de code dans son ensemble? Je pense que la réponse de @ Doval aborde ce problème.
2
Vous faites la même chose pour toutes les classes. Vous vous retrouvez avec plus de classes, toutes conformes à SRP. Cela rend la structure plus complexe du point de vue des connexions. Mais la simplicité de chaque classe le justifie. J'aime la réponse de @ Doval, cependant.
Miyamoto Akira
Je pense que vous devriez ajouter cela à votre réponse.
4

Voici les arguments qui, à mon avis, soutiennent l'affirmation selon laquelle le principe de la responsabilité unique est une bonne pratique. Je fournis également des liens vers d'autres ouvrages où vous pouvez lire des raisonnements encore plus détaillés - et plus éloquents que le mien:

  • Meilleure maintenance : idéalement, chaque fois qu'une fonctionnalité du système doit être modifiée, une seule classe doit être modifiée. Une correspondance claire entre les classes et les responsabilités signifie que tout développeur impliqué dans le projet peut identifier de quelle classe il s'agit. (Comme @ MaciejChałapuk l'a noté, voir Robert C. Martin, «Clean Code» .)

  • Tests plus faciles : idéalement, les classes devraient avoir une interface publique aussi minime que possible et les tests ne devraient concerner que cette interface publique. Si vous ne pouvez pas tester avec clarté, car de nombreuses parties de votre classe sont privées, cela signifie clairement que votre classe a trop de responsabilités et que vous devez la scinder en sous-classes plus petites. Veuillez noter que cela s’applique également aux langues dans lesquelles il n’existe aucun membre de la classe «public» ou «privé»; le fait d'avoir une petite interface publique signifie qu'il est très clair pour le code client quelles parties de la classe elle est censée utiliser. (Voir Kent Beck, "Développement piloté par les tests" pour plus de détails.)

  • Code robuste : votre code ne faillira pas plus ou moins souvent car il est bien écrit; mais, comme tout code, son but ultime n'est pas de communiquer avec la machine , mais avec d'autres développeurs (voir Kent Beck, "Modèles d'implémentation" , chapitre 1.) Une base de code claire est plus facile à raisonner, donc moins de bugs seront introduits et moins de temps s’écoulera entre la découverte d’un bogue et sa résolution.

logc
la source
Si quelque chose doit changer pour des raisons professionnelles, croyez-moi, avec SRP, vous devrez modifier plus d'une classe. Dire que si un changement de classe, c'est seulement pour une raison, ce n'est pas la même chose que s'il y a un changement, cela n'affectera qu'une classe.
Bastien Vandamme
@ B413: Je ne voulais pas dire que tout changement impliquerait un seul changement pour une seule classe. De nombreuses parties du système peuvent nécessiter des modifications pour se conformer à une seule modification de l'entreprise. Mais peut-être avez-vous quelque chose à dire et j'aurais dû écrire «chaque fois qu'une fonctionnalité du système doit être modifiée».
logc
3

Il y a un certain nombre de raisons, mais celle que j'aime bien est l'approche utilisée par plusieurs des premiers programmes UNIX: faites une chose bien. Il est déjà assez difficile de faire cela avec une seule chose, et de plus en plus difficile plus vous essayez de faire.

Une autre raison est de limiter et de contrôler les effets secondaires. J'ai adoré mon ouvre-porte de cafetière combinée. Malheureusement, le café débordait généralement lorsque j'avais des visiteurs. J'ai oublié de fermer la porte après avoir préparé du café l'autre jour et quelqu'un l'a volé.

Sur le plan psychologique, vous ne pouvez suivre qu'un petit nombre de choses à la fois. Les estimations générales sont sept plus ou moins deux. Si une classe fait plusieurs choses, vous devez les suivre toutes en même temps. Cela réduit votre capacité à suivre ce que vous faites. Si une classe fait trois choses et que vous ne voulez qu’une d’entre elles, vous pouvez épuiser votre capacité de garder une trace de ces choses avant de faire quoi que ce soit avec la classe.

Faire plusieurs choses augmente la complexité du code. Au-delà du code le plus simple, la complexité croissante augmente les risques de bogues. De ce point de vue, vous voulez que les classes soient aussi simples que possible.

Tester une classe qui fait une chose est beaucoup plus simple. Vous n'avez pas à vérifier que la deuxième chose que la classe a faite a eu lieu ou n'a pas eu lieu pour chaque test. Vous n'avez pas non plus à résoudre les problèmes et à refaire le test lorsqu'un de ces tests échoue.

BillThor
la source
2

Parce que le logiciel est organique. Les exigences changent constamment, vous devez donc manipuler des composants avec le moins de maux de tête possible. En ne suivant pas les principes de SOLID, vous risquez de vous retrouver avec une base de code concrète.

Imaginez une maison avec un mur de béton porteur. Que se passe-t-il lorsque vous supprimez ce mur sans aucun support? La maison va probablement s'effondrer. Nous ne le souhaitons pas dans les logiciels. Nous structurons donc les applications de manière à ce que vous puissiez facilement déplacer / remplacer / modifier des composants sans causer beaucoup de dommages.

CodeART
la source
1

Je suis la pensée: 1 Class = 1 Job.

Utilisation d'une analogie physiologique: moteur (système neural), respiration (poumons), digestif (estomac), olfactif (observation), etc. Chacun de ces contrôleurs comportera un sous-ensemble de contrôleurs, mais chacun n'a qu'une responsabilité, que ce soit de gérer le fonctionnement de chacun de leurs sous-systèmes respectifs ou s’il s’agit d’un sous-système de point de terminaison ne réalisant qu’une tâche, telle que lever le doigt ou faire pousser un follicule pileux.

Ne confondez pas le fait qu'il peut s'agir d'un gestionnaire plutôt que d'un travailleur. Certains travailleurs finissent par être promus au poste de responsable, lorsque le travail qu'ils effectuent est devenu trop compliqué pour qu'un processus puisse le gérer seul.

La partie la plus compliquée de mon expérience est de savoir quand désigner une classe en tant que processus Opérateur, Superviseur ou Manager. Quoi qu'il en soit, vous devrez observer et indiquer ses fonctionnalités pour une responsabilité (opérateur, superviseur ou gestionnaire).

Lorsqu'un classe / objet exécute plus d'un de ces rôles, vous constaterez que le processus global commencera à avoir des problèmes de performances ou à traiter des goulots d'étranglement.

GoldBishop
la source
Même si la mise en œuvre du traitement de l'oxygène atmosphérique en hémoglobine doit être traitée comme une Lungclasse, je dirais qu'une instance de Personstill doit être immobile Breathe, et qu'elle Persondoit donc contenir suffisamment de logique pour au moins déléguer les responsabilités associées à cette méthode. De plus, je suggérerais qu'une forêt d'entités interconnectées qui ne sont accessibles que par un propriétaire commun est souvent plus facile à raisonner qu'une forêt comprenant plusieurs points d'accès indépendants.
Supercat
Oui, dans ce cas, le poumon est un gestionnaire, bien que le Villi soit un superviseur et que le processus de Respiration soit la classe des travailleurs pour transférer les particules d’air dans le flux sanguin.
GoldBishop
@GoldBishop: Peut-être le bon point de vue serait-il de dire qu'une Personclasse a pour tâche de déléguer toutes les fonctionnalités associées à une entité monstrueusement trop compliquée d '"être humain réel", y compris l'association de ses différentes parties . Avoir une entité qui fait 500 choses est moche, mais si c'est ainsi que fonctionne le système réel modélisé, il peut être préférable d'avoir une classe qui délègue 500 fonctions tout en conservant une identité, plutôt que de tout faire avec des pièces disjointes.
Supercat
@supercat Ou vous pouvez simplement compartimenter le tout et avoir une classe avec le superviseur nécessaire sur les sous-processus. De cette façon, vous pourriez (en théorie) Personséparer chaque processus de la classe de base tout en rendant compte de la réussite ou de l'échec de la chaîne, sans toutefois affecter l'autre. 500 fonctions (bien qu'elles soient définies à titre d'exemple, seraient excessives et impossibles à prendre en charge) J'essaie de garder ce type de fonctionnalité dérivée et modulaire.
GoldBishop
@GoldBishop: J'aimerais qu'il y ait un moyen de déclarer un type "éphémère", de telle sorte que du code extérieur puisse invoquer des membres dessus ou le transmettre en tant que paramètre aux propres méthodes du type, mais rien d'autre. Du point de vue de l'organisation du code, la sous-division des classes a du sens, et même avoir du code extérieur invoque des méthodes d'un niveau (par exemple, Fred.vision.lookAt(George)cela a du sens, mais permettre au code de someEyes = Fred.vision; someTubes = Fred.digestionparaître dégueulasse, car il occulte la relation entre someEyeset someTubes.
supercat
1

Surtout avec un principe aussi important que la responsabilité unique, je m'attendrais personnellement à ce que les gens adoptent ce principe pour de nombreuses raisons.

Certaines de ces raisons pourraient être:

  • Maintenance - SRP garantit que le changement de responsabilité dans une classe n'affecte pas les autres responsabilités, ce qui simplifie la maintenance. En effet, si chaque classe n'a qu'une seule responsabilité, les modifications apportées à une responsabilité sont isolées des autres responsabilités.
  • Tester - Si une classe a une responsabilité, il est beaucoup plus facile de déterminer comment tester cette responsabilité. Si une classe a plusieurs responsabilités, vous devez vous assurer que vous testez la bonne et que le test n'est pas affecté par les autres responsabilités de la classe.

Notez également que SRP est livré avec un ensemble de principes SOLID. Respecter le PÉR et ignorer le reste est aussi grave que de ne pas le faire au départ. Vous ne devez donc pas évaluer SRP en tant que tel, mais dans le contexte de tous les principes SOLID.

Euphorique
la source
... test is not affected by other responsibilities the class has, pourriez-vous s'il vous plaît élaborer sur celui-ci?
Mahdi
1

La meilleure façon de comprendre l’importance de ces principes est d’en avoir besoin.

Quand j'étais programmeur débutant, je ne pensais pas beaucoup au design. En fait, je ne savais même pas que les modèles de design existaient. Au fur et à mesure que mes programmes grandissaient, changer une chose signifiait changer beaucoup d'autres choses. Il était difficile de localiser les bogues, le code était énorme et répétitif. Il n'y avait pas beaucoup de hiérarchie d'objets, les choses étaient partout. Ajouter quelque chose de nouveau ou supprimer quelque chose de vieux entraînerait des erreurs dans les autres parties du programme. Allez comprendre.

Ce n'est peut-être pas grave pour les petits projets, mais dans les grands projets, les choses peuvent être très cauchemardesques. Plus tard, quand je suis tombé sur les concepts de modèles de design, je me suis dit: "ah oui, faire ça aurait rendu les choses tellement plus faciles alors".

Vous ne pouvez vraiment pas comprendre l'importance des modèles de conception jusqu'à ce que le besoin s'en fait sentir. Je respecte les modèles car, d’expérience, je peux affirmer qu’ils facilitent la maintenance du code et le rendent robuste.

Cependant, tout comme vous, je ne suis toujours pas sûr des "tests faciles", car je n'ai pas encore eu besoin de passer aux tests unitaires.

Harsimranb
la source
Les modèles sont en quelque sorte connectés à SRP, mais SRP n'est pas requis lors de l'application de modèles de conception. Il est possible d'utiliser des modèles et d'ignorer complètement SRP. Nous utilisons des modèles pour traiter des exigences non fonctionnelles, mais l'utilisation de modèles n'est requise dans aucun programme. Les principes sont essentiels pour rendre le développement moins douloureux et (OMI) devrait être une habitude.
Maciej Chałapuk
Je suis complètement d'accord avec toi. SRP, dans une certaine mesure, renforce la conception et la hiérarchie des objets. C'est un bon état d'esprit pour un développeur, car il doit commencer à penser en termes d'objets et de ce qu'ils devraient être. Personnellement, cela a également eu un impact positif sur mon développement.
harsimranb
1

La réponse est, comme d’autres l’ont déjà fait remarquer, qu’elles sont toutes correctes et qu’elles se nourrissent les unes des autres. Faciliter les tests facilite la maintenance, rend le code plus robuste, facilite la maintenance, etc.

Tout cela revient à un principe clé - le code doit être aussi petit et faire le moins possible pour que le travail soit effectué. Cela s'applique à une application, à un fichier ou à une classe, tout autant qu'à une fonction. Plus un code est volumineux, plus il est difficile à comprendre, à maintenir, à étendre ou à tester.

Je pense que cela peut se résumer en un mot: portée. Portez une attention particulière à la portée des artefacts: moins il y a d'éléments à portée d'une application, mieux c'est.

Portée étendue = plus de complexité = plus de moyens pour que les choses tournent mal.

Jmoreno
la source
1

Si une classe est trop grande, il devient difficile de la maintenir, de la tester et de la comprendre, d’autres réponses ont couvert cette volonté.

Il est possible qu'une classe ait plus d'une responsabilité sans problème, mais vous rencontrez rapidement des problèmes avec des classes trop complexes.

Cependant, le simple fait de définir «une seule responsabilité» facilite le fait de savoir quand vous avez besoin d'une nouvelle classe .

Cependant, définir «responsabilité» est difficile, cela ne signifie pas «faire tout ce que la spécification de l'application dit», la véritable compétence consiste à savoir comment décomposer le problème en petites unités de «responsabilité».

Ian
la source