Je suis développeur junior chez les seniors et j'ai beaucoup de mal à comprendre leur pensée, leur raisonnement.
Je lis Domain-Driven Design (DDD) et je ne comprends pas pourquoi nous devons créer autant de classes. Si nous suivons cette méthode de conception de logiciels, nous nous retrouvons avec 20-30 classes qui peuvent être remplacées par au plus deux fichiers et 3-4 fonctions. Oui, cela pourrait être compliqué, mais c'est beaucoup plus facile à gérer et à lire.
Chaque fois que je veux voir ce que fait une sorte de EntityTransformationServiceImpl
, j'ai besoin de suivre de nombreuses classes, interfaces, leurs appels de fonction, constructeurs, leur création et ainsi de suite.
Mathématiques simples:
Tout le monde dit que c'est facile à entretenir, mais pour quoi faire? Chaque fois que vous ajoutez de nouvelles fonctionnalités, vous ajoutez cinq classes supplémentaires avec des usines, des entités, des services et des valeurs. J'ai l'impression que ce type de code se déplace beaucoup plus lentement que le code en désordre.
Disons que si vous écrivez du code désordonné LOC 50K en un mois, la chose DDD nécessite beaucoup de révisions et de changements (cela ne me dérange pas de tests dans les deux cas). Un simple ajout peut prendre une semaine sinon plus.
En un an, vous écrivez beaucoup de code désordonné et vous pouvez même le réécrire plusieurs fois, mais avec le style DDD, vous n'avez toujours pas assez de fonctionnalités pour concurrencer le code désordonné.
S'il vous plaît, expliquez. Pourquoi avons-nous besoin de ce style DDD et de nombreux motifs?
UPD 1 : J'ai reçu tellement de bonnes réponses, pouvez-vous s'il vous plaît ajouter un commentaire quelque part ou modifier votre réponse avec le lien pour la liste de lecture (je ne sais pas de quelle pour commencer, DDD, Design Patterns, UML, Code Complete, Refactoring, Pragmatic, ... tant de bons livres), bien sûr avec la séquence, pour que je puisse aussi commencer à comprendre et à devenir senior comme certains d'entre vous.
Un bon ingénieur comprend qu'un problème d'optimisation n'a pas de sens sans cible. Vous ne pouvez pas simplement optimiser, vous devez optimiser pour quelque chose. Par exemple, vos options de compilateur incluent l'optimisation pour la vitesse et l'optimisation pour la taille du code; ce sont parfois des objectifs opposés.
J'aime dire à ma femme que mon bureau est optimisé pour les ajouts. C'est juste une pile et il est très facile d'ajouter des choses. Ma femme préférerait que j'optimise pour la récupération, c'est-à-dire que j'organise un peu mes affaires pour que je puisse trouver des choses. Cela rend bien sûr plus difficile à ajouter.
Le logiciel est la même manière. Vous pouvez certainement optimiser la création de produits - générer une tonne de code monolithique le plus rapidement possible, sans vous soucier de son organisation. Comme vous l'avez déjà remarqué, cela peut être très, très rapide. L'alternative consiste à optimiser pour la maintenance - rendre la création plus difficile, mais rendre les modifications plus faciles ou moins risquées. C'est le but du code structuré.
Je dirais qu'un produit logiciel réussi ne sera créé qu'une seule fois mais modifié de très nombreuses fois. Les ingénieurs expérimentés ont vu des bases de code non structurées prendre leur vie en main et devenir des produits, augmentant en taille et en complexité, jusqu'à ce que même de petits changements soient très difficiles à effectuer sans introduire d'énormes risques. Si le code était structuré, le risque peut être contenu. C'est pourquoi nous allons à tous ces ennuis.
Je remarque dans votre analyse que vous regardez les quantités - quantité de code, nombre de classes, etc. Bien que ceux-ci soient en quelque sorte intéressants, l'impact réel provient des relations entre les éléments, qui explosent de manière combinatoire. Par exemple, si vous avez 10 fonctions et aucune idée qui dépend de laquelle, vous avez 90 relations (dépendances) possibles dont vous devez vous soucier - chacune des dix fonctions peut dépendre de l'une des neuf autres fonctions, et 9 x 10 = 90. Vous n'avez peut-être aucune idée des fonctions qui modifient les variables ou de la façon dont les données sont transmises, les codeurs doivent donc s'inquiéter lors de la résolution d'un problème particulier. En revanche, si vous avez 30 classes mais qu'elles sont bien organisées, elles peuvent avoir aussi peu que 29 relations, par ex. s'ils sont superposés ou disposés en pile.
Comment cela affecte-t-il le débit de votre équipe? Eh bien, il y a moins de dépendances, le problème est beaucoup plus traitable; les codeurs n'ont pas à jongler avec un million de choses dans leur tête chaque fois qu'ils font un changement. Ainsi, la minimisation des dépendances peut être un énorme coup de pouce à votre capacité à raisonner avec compétence sur un problème. C'est pourquoi nous divisons les choses en classes ou modules, et les variables de portée aussi étroitement que possible, et utilisons les principes SOLID .
Eh bien, tout d'abord, la lisibilité et la maintenabilité sont souvent dans l'œil du spectateur.
Ce qui est lisible pour vous peut ne pas l'être pour votre voisin.
La maintenabilité se résume souvent à la découvrabilité (la facilité avec laquelle un comportement ou un concept est découvert dans la base de code) et la découvrabilité est une autre chose subjective.
DDD aide les équipes de développeurs à proposer une manière spécifique (mais toujours subjective) d'organiser vos concepts et comportements de code. Cette convention facilite la découverte des choses, et donc la maintenance de l'application.
Cet arrangement n'est pas objectivement plus facile à maintenir. Il est cependant mesurable plus facile à maintenir lorsque tout le monde comprend qu'ils fonctionnent dans un contexte DDD.
Les classes aident à la maintenabilité, la lisibilité, la découvrabilité, etc ... car elles sont une convention bien connue .
Dans les paramètres orientés objet, les classes sont généralement utilisées pour regrouper des comportements étroitement liés et pour encapsuler un état qui doit être soigneusement contrôlé.
Je sais que cela semble très abstrait, mais vous pouvez y penser de cette façon:
Avec les classes, vous n'avez pas nécessairement besoin de savoir comment le code qu'elles contiennent fonctionne. Vous avez juste besoin de savoir quoi dont la classe est responsable.
Les cours vous permettent de raisonner votre application en termes d'interactions entre composants bien définis .
Cela réduit la charge cognitive lors du raisonnement sur le fonctionnement de votre application. Au lieu de devoir vous souvenir de ce que 600 lignes de code accomplit, vous pouvez penser à la façon dont composants interagissent.
Et, étant donné que ces 30 composants s'étendent probablement sur couches de votre application, vous n'avez probablement à raisonner qu'environ 10 composants à la fois.
Cela semble assez gérable.
Essentiellement, ce que vous voyez faire par les développeurs seniors est le suivant:
Ils décomposent l'application en facile à raisonner classes.
Ils les organisent ensuite en facile à raisonner couches.
Ils le font parce qu'ils savent qu'au fur et à mesure que l'application se développe, il devient de plus en plus difficile de raisonner sur l'ensemble. Le décomposer en couches et classes signifie qu'ils n'ont jamais à raisonner sur l'application entière. Ils n'ont besoin que de raisonner sur un petit sous-ensemble.
Expliquez-moi, pourquoi avons-nous besoin de ce style DDD, de nombreux modèles?
Tout d'abord, une note: la partie importante de DDD n'est pas les modèles , mais l'alignement de l'effort de développement avec l'entreprise. Greg Young a fait remarquer que les chapitres du livre bleu sont dans dans le mauvais ordre .
Mais pour votre question spécifique: il y a en général beaucoup plus de classes que vous ne le pensez, (a) parce qu'un effort est fait pour distinguer le comportement du domaine de la plomberie et (b) parce que des efforts supplémentaires sont faits pour garantir que les concepts dans le modèle de domaine sont exprimés explicitement.
Franchement, si vous avez deux concepts différents dans le domaine, alors ils devraient être distincts dans le modèle même s'ils se partagent la même chose dans la représentation en mémoire .
En effet, vous créez un langage spécifique au domaine qui décrit votre modèle dans le langage de l'entreprise, de sorte qu'un expert en domaine devrait être en mesure de l'examiner et de détecter les erreurs.
De plus, vous voyez un peu plus d'attention accordée à la séparation des préoccupations; et la notion d'isoler les consommateurs d'une certaine capacité des détails de mise en œuvre. Voir D. L. Parnas . Les limites claires vous permettent de modifier ou d'étendre l'implémentation sans que les effets ne se répercutent sur l'ensemble de votre solution.
La motivation ici: pour une application qui fait partie de la compétence de base d'une entreprise (c'est-à-dire un endroit où elle tire un avantage concurrentiel), vous voudrez pouvoir remplacer facilement et à moindre coût un comportement de domaine par une meilleure variation. En effet, vous avez des parties du programme que vous souhaitez faire évoluer rapidement (comment l'état évolue au fil du temps), et d'autres parties que vous voulez changer lentement (comment l'état est stocké); les couches supplémentaires d'abstraction évitent de les coupler par inadvertance.
En toute justice: certains d'entre eux sont également des lésions cérébrales orientées objet. Les modèles décrits à l'origine par Evans sont basés sur Java projets auxquels il a participé il y a plus de 15 ans; l'état et le comportement sont étroitement liés dans ce style, ce qui entraîne des complications que vous préféreriez peut-être éviter; voir Perception et Action par Stuart Halloway, ou Inlining Code par John Carmack.
Peu importe la langue dans laquelle vous travaillez, la programmation dans un style fonctionnel offre des avantages. Vous devriez le faire chaque fois que cela vous convient, et vous devriez réfléchir sérieusement à la décision quand elle ne vous convient pas. Carmack, 2012
Il y a beaucoup de bons points dans les autres réponses, mais je pense qu'ils manquent ou ne mettent pas l'accent sur une importante erreur conceptuelle que vous faites:
Vous comparez l'effort pour comprendre le programme complet.
Ce n'est pas une tâche réaliste avec la plupart des programmes. Même les programmes simples contiennent tellement de code qu'il est tout simplement impossible de tout gérer dans la tête à un moment donné. Votre seule chance est de trouver la partie d'un programme qui est pertinente pour la tâche à accomplir (correction d'un bogue, implémentation d'une nouvelle fonctionnalité) et de travailler avec cela.
Si votre programme se compose d'énormes fonctions/méthodes/classes, cela est presque impossible. Vous devrez comprendre des centaines de lignes de code juste pour décider si ce morceau de code est pertinent pour votre problème. Avec les estimations que vous avez données, il devient facile de passer une semaine juste pour trouver le morceau de code sur lequel vous devez travailler.
Comparez cela à une base de code avec de petites fonctions/méthodes/classes nommées et organisées en packages/espaces de noms qui rendent évident où trouver/mettre un morceau de logique donné. Lorsque bien fait dans de nombreux cas, vous pouvez sauter directement au bon endroit pour résoudre votre problème, ou au moins à un endroit où le démarrage de votre débogueur vous amènera au bon endroit en quelques sauts.
J'ai travaillé dans les deux types de systèmes. La différence peut facilement être de deux ordres de grandeur dans les performances pour des tâches comparables et une taille de système comparable.
L'effet que cela a sur d'autres activités:
De nombreuses réponses ont donné un bon raisonnement du point de vue du développeur - que la maintenance peut être réduite, au prix de rendre le code plus difficile à écrire en premier lieu.
Cependant, il y a un autre aspect à considérer - les tests ne peuvent être affinés que comme votre code d'origine.
Si vous écrivez tout dans un monolithe, le seul test efficace que vous pouvez écrire est "étant donné ces entrées, la sortie est-elle correcte?". Cela signifie que tous les bogues trouvés sont limités à "quelque part dans cette pile géante de code".
Bien sûr, vous pouvez ensuite demander à un développeur de s'asseoir avec un débogueur et de trouver exactement où le problème a commencé et de travailler sur un correctif. Cela prend beaucoup de ressources et est une mauvaise utilisation du temps d'un développeur. Imaginez que vous ayez un bug mineur, qui oblige un développeur à déboguer à nouveau le programme entier.
La solution: de nombreux tests plus petits qui identifient chacun une défaillance potentielle spécifique.
Ces petits tests (par exemple les tests unitaires) ont l'avantage de vérifier une zone spécifique de la base de code et d'aider à trouver des erreurs dans une portée limitée. Cela accélère non seulement le débogage lorsqu'un test échoue, mais signifie également que si tous vos petits tests échouent - vous pouvez plus facilement trouver l'échec dans vos tests plus importants (c'est-à-dire que s'il ne se trouve pas dans une fonction testée spécifique, il doit être dans l'interaction entre eux).
Comme il doit être clair, pour effectuer des tests plus petits, votre base de code doit être divisée en petits morceaux testables. La façon de le faire, sur une grande base de code commerciale, aboutit souvent à un code qui ressemble à ce avec quoi vous travaillez.
Juste comme note secondaire: Cela ne veut pas dire que les gens n'iront pas trop loin. Mais il y a une raison légitime de séparer les bases de code en parties plus petites/moins connectées - si cela est fait de manière raisonnable.
Expliquez-moi, pourquoi avons-nous besoin de ce style DDD, de nombreux modèles?
Beaucoup (la plupart ...) d'entre nous n'en ont vraiment pas besoin eux. Les théoriciens et les programmeurs expérimentés très avancés écrivent des livres sur les théories et les méthodologies à la suite de nombreuses recherches et de leur expérience approfondie - cela ne signifie pas que tout ce qu'ils écrivent est applicable à chaque programmeur dans leur pratique quotidienne.
En tant que développeur junior, il est bon de lire des livres comme celui que vous avez mentionné pour élargir vos perspectives et vous sensibiliser à certains problèmes. Cela vous évitera également d'être embarrassé et déconcerté lorsque vos collègues seniors utilisent une terminologie que vous ne connaissez pas. Si vous trouvez quelque chose de très difficile et ne semble pas avoir de sens ou ne semble pas utile, ne vous tuez pas dessus - limez-le simplement dans votre tête qu'il existe un tel concept ou une telle approche.
Dans votre développement quotidien, à moins que vous ne soyez un universitaire, votre travail consiste à trouver des solutions viables et maintenables. Si les idées que vous trouvez dans un livre ne vous aident pas à atteindre cet objectif, ne vous en faites pas pour l'instant, tant que votre travail est jugé satisfaisant.
Il peut arriver un moment où vous découvrirez que vous pouvez utiliser une partie de ce que vous lisez, mais que vous n'avez pas tout à fait "compris" au début, ou peut-être pas.
Comme votre question couvre beaucoup de terrain, avec beaucoup d'hypothèses, je vais préciser le sujet de votre question:
Pourquoi avons-nous besoin de tant de classes dans les modèles de conception
Nous ne faisons pas. Il n'y a pas de règle généralement acceptée qui dit qu'il doit y avoir de nombreuses classes dans les modèles de conception.
Il existe deux guides clés pour décider où placer le code et comment découper vos tâches en différentes unités de code:
Pourquoi ces deux devraient-ils être importants?
Ces deux aspects sont les "pilotes" de base pour tout choix de "où mettre quoi" dans n'importe quel langage de programmation et tout paradigme (pas seulement OO). Tout le monde ne les connaît pas explicitement, et il faut du temps, souvent des années, pour avoir une idée vraiment enracinée et automatique de la manière dont ces logiciels influencent les logiciels.
De toute évidence, ces deux concepts ne vous disent rien sur quoi faire faire. Certaines personnes se trompent du côté de trop, d'autres du côté de trop peu. Certains langages (en vous regardant ici, Java) ont tendance à favoriser de nombreuses classes en raison de la nature extrêmement statique et pédante du langage lui-même (ce n'est pas une déclaration de valeur, mais c'est ce qu'elle est). Cela devient particulièrement visible lorsque vous le comparez à des langages dynamiques et plus expressifs, par exemple Ruby.
Un autre aspect est que certaines personnes souscrivent à l'approche agile consistant à n'écrire que du code qui est nécessaire en ce moment, et à refactoriser fortement plus tard, si nécessaire. Dans ce style de développement, vous ne créeriez pas de interface
si vous n'avez qu'une seule classe d'implémentation. Vous implémenteriez simplement la classe concrète. Si, plus tard, vous avez besoin d'une deuxième classe, vous refactoriseriez.
Certaines personnes ne fonctionnent tout simplement pas de cette façon. Ils créent des interfaces (ou, plus généralement, des classes de base abstraites) pour tout ce qui pourrait être utilisé plus généralement; cela conduit rapidement à une explosion de classe.
Encore une fois, il y a des arguments pour et contre, et peu importe moi ou vous préférez. Dans votre vie de développeur de logiciels, vous rencontrerez tous les extrêmes, des longues méthodes de spaghetti, en passant par des conceptions de classe éclairées et juste assez grandes, jusqu'aux schémas de classe incroyablement explosifs qui sont beaucoup trop élaborés. À mesure que vous devenez plus expérimenté, vous évoluerez davantage vers des rôles "architecturaux" et pourrez commencer à influencer cela dans la direction que vous souhaitez. Vous découvrirez un milieu doré pour vous-même, et vous constaterez toujours que beaucoup de gens seront en désaccord avec vous, quoi que vous fassiez.
Par conséquent, garder l'esprit ouvert est le point le plus important ici, et ce serait mon principal conseil pour vous, étant donné que vous semblez beaucoup souffrir de la question, à en juger par le reste de votre question ...
Les codeurs expérimentés ont appris:
Les réponses jusqu'à présent, toutes bonnes, ayant commencé avec l'hypothèse raisonnable que le demandeur manque quelque chose, ce qu'il reconnaît également. Il est également possible que le demandeur ait fondamentalement raison, et il vaut la peine de discuter de la façon dont cette situation peut se produire.
L'expérience et la pratique sont puissantes et si les personnes âgées ont acquis leur expérience dans de grands projets complexes où la seule façon de garder les choses sous contrôle est avec beaucoup de EntityTransformationServiceImpl
, alors elles deviennent rapides et à l'aise avec les modèles de conception et l'adhésion étroite à DDD. Ils seraient beaucoup moins efficaces en utilisant une approche légère, même pour les petits programmes. En tant qu'étranger, vous devez vous adapter et ce sera une excellente expérience d'apprentissage.
Tout en vous adaptant, vous devez le prendre comme une leçon sur l'équilibre entre l'apprentissage approfondi d'une seule approche, au point que vous pouvez la faire fonctionner n'importe où, par opposition à rester polyvalent et à savoir quels outils sont disponibles sans nécessairement être un expert avec l'un d'eux. Il y a des avantages aux deux et les deux sont nécessaires dans le monde.
Étant donné que mon message initial de cette réponse n'a pas atteint ce que je voulais, j'ai acheté la version pour Kindle de ce livre, et j'ai trouvé exactement ce dont je me souvenais vaguement, afin de répondre directement à la question
Pourquoi avons-nous besoin de tant de classes dans les modèles de conception?
Court et doux - nous ne le faisons pas. Exactement pourquoi c'est vrai, je ne suis pas sûr de pouvoir mettre des mots aussi bien que ce livre, et c'était l'essentiel de ma réponse initialement publiée.
Ce qui suit sont des extraits du livre qui parlent bien directement de la question.
Cet extrait du livre est extrait du chapitre 3 "Modèles" et du sous-chapitre 3 "Il existe de nombreuses façons de mettre en œuvre un modèle":
Dans ce livre, vous trouverez des implémentations de modèles qui sont très différentes de leurs diagrammes de structure dans Design Patterns. Par exemple, voici le diagramme de structure du motif composite:
Et voici une implémentation particulière du pattern Composite:
Comme vous pouvez le voir, cette implémentation de Composite ressemble peu au diagramme de structure de Composite. Il s'agit d'une implémentation composite minimaliste résultant du codage uniquement de ce qui était nécessaire.
Être minimaliste dans vos implémentations de modèles fait partie de la pratique de la conception évolutive. Dans de nombreux cas, une implémentation non basée sur des modèles peut devoir évoluer pour inclure un modèle. Dans ce cas, vous pouvez refactoriser la conception en une simple mise en œuvre de modèle. J'utilise cette approche tout au long de ce livre.
Un peu plus tôt dans le sous-chapitre, Joshua dit ceci:
Ce diagramme indique que les classes Creator et Product sont abstraites, tandis que les classes ConcreteCreator et ConcreteProduct sont concrètes. Est-ce la seule façon d'implémenter le modèle de méthode d'usine? En aucun cas! En fait, les auteurs de Design Patterns s'efforcent d'expliquer les différentes façons d'implémenter chaque modèle dans une section intitulée Implementation Notes. Si vous lisez les notes d'implémentation du modèle de méthode d'usine, vous constaterez qu'il existe de nombreuses façons d'implémenter une méthode d'usine.
Voici toute cette section, qui va dans un peu plus de détails:
[POSTE ORIGINAL]
Je voudrais vous indiquer un livre qui, je pense, répond à votre question avec plus d'un sens heuristique de quand il est nécessaire, et quand c'est exagéré, d'utiliser "tant de classes" dans les modèles de conception, car cet auteur implémente souvent un modèle de conception très frugalement, avec juste quelques "pièces" orientées objet.
Ce livre fait partie de la série Signature Martin Fowler et est un excellent livre sur les modèles de conception de Joshua Kerievsky, intitulé "Refactoring To Patterns" , mais il s'agit vraiment de "Right-Factoring" to and/ou loin des motifs. Cela valait bien le prix, pour moi.
Dans son livre, il a parfois mis en œuvre des modèles sans utiliser l'orientation d'objet, et a plutôt utilisé quelques champs et quelques méthodes, par exemple. Et il supprimait parfois un motif parce qu'il était excessif. J'ai donc adoré son traitement du sujet parce que "l'équilibre" entre le moment de modeler et le moment d'aller plus léger était un cadeau qu'il a donné au lecteur.
Voici une capture d'écran de la partie la plus pertinente de la table des matières. C'était l'un de mes meilleurs achats.
Et hors de cette table des matières, j'insiste particulièrement sur ce qui suit:
Donc, je pense que votre question peut être paraphrasée comme un sens interne du questionnement "Faut-il vraiment utiliser autant de classes pour implémenter des modèles?" et la réponse est "parfois", et bien sûr, le "ça dépend" qui semble toujours présent. Mais il faut plus d'un livre pour vous répondre, donc je recommande que vous obteniez celui-ci si vous le pouvez. (Je ne l'ai pas avec moi pour le moment, sinon je vous en donnerais un bon exemple - désolé.) HTH.