Le principe est défini comme des modules ayant une raison de modifier . Ma question est que ces raisons de modifier ne sont sûrement pas connues que le code ne commence à changer ?? À peu près, chaque morceau de code a de nombreuses raisons pour lesquelles elle pourrait éventuellement changer, mais essentiellement tenter de anticiper tous ces éléments et de concevoir votre code dans cet esprit se terminerait. avec un très mauvais code. N'est-ce pas une meilleure idée de ne pas vraiment commencer à appliquer SRP lorsque les demandes de modification du code commencent à venir? Plus spécifiquement, lorsqu'un code de code a changé plus d'une fois pour plus d'une raison, prouvant ainsi qu'il a plus d'une raison de changer. Cela semble très anti-agile pour tenter de deviner des raisons de changement.
Un exemple serait un morceau de code qui imprime un document. Une demande consiste à le modifier pour imprimer à PDF et ensuite une deuxième demande est faite pour la modifier pour appliquer une mise en forme différente au document. À ce stade, vous avez une preuve de plus d'un seul raison de changer (et de violation de SRP) et devrait faire le refactoring approprié.
Bien sûr, le [~ # ~ # ~] Yagni [~ # ~ ~] Principe vous dira d'appliquer SRP PAS avant de vraiment en avoir besoin. Mais la question que vous devriez vous poser est: Dois-je appliquer d'abord SRP et seulement lorsque je dois réellement changer mon code?
À mon expérience, l'application de SRP vous offre beaucoup plus tôt: lorsque vous devez trouver Où et Comment Pour appliquer un changement spécifique de votre code . Pour cette tâche, vous devez lire et comprendre vos fonctions et classes existantes. Cela devient beaucoup plus facile lorsque toutes vos fonctions et classes ont une responsabilité spécifique. Donc, vous devez appliquer SRP chaque fois que votre code facilite votre code, chaque fois que vos fonctions sont plus petites et plus elles-mêmes. La réponse est donc Oui, il est logique d'appliquer SRP même pour le nouveau code.
Par exemple, lorsque votre code d'impression lit un document, format le document et Imprime le résultat à un périphérique spécifique, il s'agit de 3 responsabilités claires séparables. Donc, faites au moins 3 fonctions hors d'entre eux, donnez-leur des noms. Par exemple:
void RunPrintWorkflow()
{
var document = ReadDocument();
var formattedDocument = FormatDocument(document);
PrintDocumentToScreen(formattedDocument);
}
Maintenant, lorsque vous obtenez une nouvelle exigence de modifier le formatage du document ou un autre pour imprimer sur pdf, vous savez Exactement à laquelle de ces fonctions ou emplacements dans le code, vous devez appliquer des modifications, et encore plus important, où pas.
Donc, chaque fois que vous venez à une fonction, vous ne comprenez pas parce que la fonction est "trop", et vous n'êtes pas sûr si et où appliquer un changement, alors Considérez le refacteur de la fonction dans des fonctions séparées et plus petites. N'attendez pas que vous deviez changer quelque chose. Le code est 10 fois plus souvent lu que modifié et les fonctions plus petites sont beaucoup plus faciles à lire. À mon expérience, lorsqu'une fonction a une certaine complexité, vous pouvez TOUJOURS Divisez la fonction en différentes responsabilités, indépendant de savoir quels changements viendront à l'avenir. Bob Martin va généralement une étape plus loin, voir le lien que j'ai donné dans mes commentaires ci-dessous.
EDIT: à votre commentaire: la responsabilité principale de la fonction extérieure dans l'exemple ci-dessus n'est pas d'imprimer sur un périphérique spécifique ou de formater le document - il s'agit de Intégrer le flux de travail d'impression. Ainsi, au niveau de l'abstraction de la fonction extérieure, une nouvelle exigence comme "Docs ne doit plus être formatée" ou "DOC doit être envoyée au lieu d'imprimé" est juste "la même raison" - à savoir "Le flux de travail d'impression a changé". Si nous parlons de choses comme ça, il est important de coller au niveau droit d'abstraction.
Je pense que vous êtes mal compris par SRP.
La seule raison du changement ne consiste pas à changer le code, mais à ce que votre code fait.
Je pense que la définition du SRP comme "ayant une raison de changer" est trompeuse pour exactement cette raison. Prenez-le exactement à la valeur nominale: le principe de responsabilité unique indique qu'une classe ou une fonction devrait avoir exactement une responsabilité. Avoir une seule raison de changer est un effet secondaire de faire une seule chose pour commencer. Il n'y a aucune raison que vous ne pouvez pas au moins faire un effort à la responsabilité unique dans votre code sans rien savoir comment cela pourrait changer à l'avenir.
L'un des meilleurs indices pour ce type de chose est lorsque vous choisissez des noms de classe ou de fonction. S'il n'apparaît pas immédiatement ce que la classe doit être nommée, sinon le nom est particulièrement long/complexe, ou le nom utilise des termes génériques tels que "Manager" ou "utilitaire", puis il s'agit probablement de SRP. De même, lors de la documentation de l'API, il devrait devenir rapidement évident si vous violez SRP en fonction de la fonctionnalité que vous décrivez.
Il y a bien sûr des nuances à SRP que vous ne pouvez pas savoir avant plus tard dans le projet - ce qui semblait être une seule responsabilité s'est avéré être deux ou trois. Ce sont des cas où vous devrez refroidir pour mettre en œuvre SRP. Mais cela ne signifie pas que SRP devrait être ignoré jusqu'à ce que vous obteniez une demande de changement; qui défait le but de SRP!
Pour parler directement à votre exemple, envisagez de documenter votre méthode d'impression. Si vous dites "Cette méthode formate les données pour l'impression et l'envoie à l'imprimante" que --et est ce qui vous obtient: ce n'est pas une responsabilité unique, c'est deux responsabilités: formatage et envoi à la imprimante. Si vous le reconnaissez et divisez-les en deux fonctions/classes, alors lorsque vos demandes de changement sont venues, vous n'avez déjà qu'une seule raison pour que chaque section change.
Un exemple serait un morceau de code qui imprime un document. Une demande consiste à le modifier pour imprimer à PDF et ensuite une deuxième demande est faite pour la modifier pour appliquer une mise en forme différente au document. À ce stade, vous avez une preuve de plus d'un seul raison de changer (et de violation de SRP) et devrait faire le refactoring approprié.
J'ai tellement de fois me tiré à pied en dépensant trop de temps en adaptant le code pour accueillir ces changements. Au lieu de simplement imprimer le fichu PDF stupide.
Le modèle d'utilisation unique peut créer un bloat de code. Lorsque des colis sont pollués avec de petites classes spécifiques qui créent une pile de déchets de code qui n'a pas de sens individuellement. Vous devez ouvrir des dizaines de fichiers source juste pour comprendre comment il arrive à la partie d'impression. En plus de cela, il peut y avoir des centaines de milliers de lignes de code qui sont en place pour exécuter 10 lignes de code qui font l'impression réelle.
Le modèle d'utilisation unique était destiné à réduire le code source et à améliorer la réutilisation du code. Il était censé créer une spécialisation et des implémentations spécifiques. Une sorte de bullseye
dans le code source pour vous à go to specific tasks
. Quand il y avait un problème d'impression, vous saviez exactement où aller pour le réparer.
Oui, vous avez un code qui imprime déjà un document. Oui, vous devez maintenant modifier le code pour imprimer également les PDF. Oui, vous devez maintenant modifier la mise en forme du document.
Êtes-vous sûr que le usage
a changé de manière significative?
Si le refactoring provoque des sections du code source pour devenir trop généralisées. Au point que l'intention originale de printing stuff
n'est plus explicite, alors vous avez créé une fracturation ambiguë dans le code source.
Le nouveau gars sera-t-il capable de comprendre cela rapidement?
Maintenez toujours votre code source dans la plus facile à comprendre l'organisation.
Bien trop souvent, j'ai vu des développeurs mis sur une oculaire et concentrez-vous sur les petits détails au point que personne d'autre ne pouvait remettre les morceaux à nouveau, si cela tombe en morceaux.
Une raison de changement est, finalement, un changement de spécification ou d'informations sur l'environnement dans lequel la demande fonctionne. Donc, un principe de responsabilité unique vous indique d'écrire chaque composant (classe, fonction, module, service ...) afin qu'il doit considérer le moins de spécifications et de l'environnement d'exécution possible.
Depuis que vous connaissez la spécification et l'environnement lorsque vous écrivez le composant, vous pouvez appliquer le principe.
Si vous envisagez l'exemple du code qui imprime un document. Vous devriez déterminer si vous pouvez définir le modèle de mise en page sans envisager le document finir au format PDF. Vous pouvez, alors SRP vous dit que vous devriez.
Bien sûr, Yagni vous dit que vous ne devriez pas. Vous devez trouver un équilibre entre les principes de conception.
Le fluge est dirigé dans la bonne direction. Le "principe de responsabilité unique" s'appliquait à l'origine aux procédures. Par exemple, Dennis Ritchie dirait qu'une fonction devrait faire une chose et le faire bien. Ensuite, en C++, Bjarne Stroustrup dirait qu'une classe devrait faire une chose et le faire bien.
Notez que, à l'exception des règles de pouce, ces deux ont officiellement peu ou rien à voir les uns avec les autres. Ils ne traitent que de ce qui est pratique à exprimer dans le langage de programmation. Eh bien, c'est quelque chose. Mais c'est une histoire très différente de celle de la conduite.
Les implémentations modernes (c'est-à-dire agiles et DDD) se concentrent davantage sur ce qui est important pour l'entreprise que sur ce que le langage de programmation peut exprimer. La partie surprenante est que les langues de programmation n'ont pas encore été rattrapées. Les langues anciennes de type Fortran capturent des responsabilités qui correspondent aux principaux modèles conceptuels de l'époque: les processus que l'on s'appliquait à chaque carte puisqu'il a traversé le lecteur de carte ou (comme dans c) le traitement qui a accompagné chaque interruption. Ensuite, il est venu d'adter les langues, qui avaient mûri au point de capturer ce que les gens du DDD réinventeraient plus tard comme étant importants (bien que Jim voisins aient eu la majeure partie de celle-ci, publiée et utilisée par 1968): Qu'est-ce que nous appelons aujourd'hui des cours . (Ils ne sont pas des modules.)
Cette étape était moins une évolution qu'un swing pendulum. Comme le pendule a balancé aux données, nous avons perdu la modélisation de cas d'utilisation inhérente à Fortran. C'est bien lorsque votre objectif principal implique les données ou les formes sur un écran. C'est un excellent modèle pour des programmes tels que PowerPoint, ou du moins pour ses opérations simples.
Ce qui a été perdu, c'est responsabilités du système. Nous ne vendons pas les éléments de DDD. Et nous ne sommes pas des méthodes de classe. Nous vendons des responsabilités du système. À un certain niveau, vous devez concevoir votre système autour du principe de responsabilité unique.
Donc, si vous regardez des personnes comme Rebecca Wirfs-Brock, ou moi, qui parlaient de méthodes de classe, nous parlons maintenant en termes d'utilisation. C'est ce que nous vendons. Ce sont les opérations système. Un cas d'utilisation devrait avoir une responsabilité unique. Un cas d'utilisation est rarement une unité architecturale. Mais tout le monde essayait de prétendre que c'était. Monsieur le Témoin The SOA personnes, par exemple.
C'est pourquoi je suis enthousiasmé par l'architecture DCI de Trygve Reenskaug - ce qui est décrit dans le livre de l'architecture maigre ci-dessus. Cela donne enfin une réelle stature à ce qui était utilisé comme une obéisse arbitraire et mystique à une "responsabilité unique" - comme on trouve dans la majeure partie de l'argumentation ci-dessus. Cette stature concerne les modèles mental humains: les utilisateurs finaux d'abord et les programmeurs seconde. Il concerne les préoccupations commerciales. Et, presque par hasard, il résume le changement à mesure que la chance nous conteste.
Le principe de la responsabilité unique Comme nous le savons, c'est un dinosaure à partir de ses jours d'origine ou d'un cheval de passe-temps que nous utilisons comme substitut à la compréhension. Vous devez laisser quelques-uns de ces chevaux de loisirs pour faire de super logiciels. Et cela nécessite de penser à la boîte. Garder les choses simples et faciles à comprendre que lorsque le problème est simple et facile à comprendre. Je ne suis pas terriblement intéressé par ces solutions: ils ne sont pas typiques, et ce n'est pas là que réside le défi.
Lorsque vous concevez un nouveau système, il est sage d'envisager le type de changements que vous devrez peut-être faire au cours de sa vie et de la façon dont ceux-ci recevront l'architecture que vous mettez en place. La scission de votre système dans les modules est une décision coûteuse de vous tromper.
Une bonne source d'information est le modèle mental de la tête des experts du domaine de l'entreprise. Prenez l'exemple du document, le formatage et le PDF. Les experts du domaine vous diront probablement qu'ils formatent leurs lettres à l'aide de modèles de document. Soit sur la stationnaire ou en mot ou autre chose. Vous pouvez récupérer ces informations avant de commencer le codage et l'utiliser dans votre conception.
Une grande lecture sur ces choses: l'architecture maigre par Coplien
Oui, le principe de la responsabilité unique doit être appliqué au nouveau code.
Mais! Qu'est-ce qu'une responsabilité?
"Imprime un rapport une responsabilité"? Je crois que la réponse est "peut-être".
Essayons d'utiliser la définition de SRP comme "n'ayant qu'une seule raison de changer".
Supposons que vous ayez une fonction qui imprime des rapports. Si vous avez deux changements:
Ensuite, le premier changement est "Modifier le style de rapport" L'autre est "Modifier le format de sortie du rapport" et vous devez maintenant les mettre dans deux fonctions différentes, car ce sont des choses différentes.
Mais si votre deuxième changement aurait été:
2b. changer cette fonction parce que votre rapport a besoin d'une police différente
Je dirais que les deux changements sont "Modifier le style de rapport" et ils peuvent rester dans une seule fonction.
Alors, où en sommes-nous? Comme d'habitude, vous devriez essayer de garder les choses simples et faciles à comprendre. Si vous modifiez la couleur de fond, 20 lignes de code et la modification de la police signifie 20 lignes de code, faites-la à nouveau deux fonctions. S'il s'agit d'une ligne chacune, gardez-la en une.
"Imprimer" ressemble beaucoup à "View" en MVC. Toute personne qui comprend les bases des objets comprendrait cela.
C'est un système responsabilité. Il est mis en œuvre comme mécanisme - MVC - qui implique une imprimante (la vue), la chose étant imprimée (le module) et la requête et les options de l'imprimante (à partir du contrôleur).
Essayer de localiser cela comme une responsabilité de classe ou de module est l'asinine et reflète la pensée de 30 ans. Nous avons beaucoup appris depuis lors, et il est amplement mis en évidence dans la littérature et dans le code des programmeurs matures.