Je suis un développeur de longue date (j'ai 49 ans) mais plutôt nouveau pour le développement orienté objet. Je lis sur OO depuis Eiffel de Bertrand Meyer, mais j'ai fait très peu de programmation OO.
Le fait est que chaque livre sur la conception OO commence par un exemple de bateau, de voiture ou de tout objet commun que nous utilisons très souvent, et ils commencent à ajouter des attributs et des méthodes, et expliquent comment ils modélisent l'état de l'objet et ce qui peut être fait avec il.
Donc, ils vont généralement quelque chose comme "meilleur est le modèle, mieux il représente l'objet dans l'application et mieux tout sort".
Jusqu'ici tout va bien, mais, d'autre part, j'ai trouvé plusieurs auteurs qui donnent des recettes telles que "une classe devrait tenir sur une seule page" (j'ajouterais "sur quelle taille de moniteur?" Maintenant que nous essayons de ne pas pour imprimer le code!).
Prenons par exemple une PurchaseOrder
classe, qui a une machine à états finis contrôlant son comportement et une collection de PurchaseOrderItem
, l'un des arguments ici au travail est que nous devrions utiliser une PurchaseOrder
classe simple, avec quelques méthodes (un peu plus qu'une classe de données), et avoir une PurchaseOrderFSM
«classe expert» qui gère la machine à états finis pour le PurchaseOrder
.
Je dirais que cela tombe dans la classification «Feature Envy» ou «Inapproprié Intimité» du message Code Smells de Jeff Atwood sur Coding Horror. J'appellerais ça du bon sens. Si je peux émettre, approuver ou annuler mon vrai bon de commande, la PurchaseOrder
classe devrait avoir issuePO
, approvePO
et les cancelPO
méthodes.
Cela ne va-t-il pas avec les principes séculaires de «maximiser la cohésion» et de «minimiser le couplage» que je considère comme les pierres angulaires de l'OO?
D'ailleurs, cela ne contribue-t-il pas à la maintenabilité de la classe?
la source
PurchaseOrder
, vous pouvez simplement nommer vos méthodesissue
,approve
etcancel
. LePO
suffixe n'ajoute qu'une valeur négative.Réponses:
Une classe doit utiliser le principe de responsabilité unique. La plupart des classes très importantes que j'ai vues font beaucoup de choses, c'est pourquoi elles sont trop grandes. Examinez chaque méthode et chaque code pour décider s'il doit être dans cette classe ou séparé, le code en double est un indice. Vous pouvez avoir une méthode issuePO mais contient-elle 10 lignes de code d'accès aux données par exemple? Ce code ne devrait probablement pas être là.
la source
À mon avis, la taille de la classe n'a pas d'importance tant que les variables, fonctions et méthodes sont pertinentes pour la classe en question.
la source
Le plus gros problème avec les grandes classes est quand il s'agit de tester ces classes, ou leur interaction avec d'autres classes. La grande taille des classes n'est pas en soi un problème, mais comme toutes les odeurs, elle indique un problème potentiel . D'une manière générale, lorsque la classe est grande, cela indique que la classe fait plus qu'une classe seule.
Pour l'analogie avec votre voiture, si la classe
car
est trop grande, c'est probablement une indication qu'elle doit être divisée en classeengine
, classedoor
s et classewindshield
s, etc.la source
Je ne pense pas qu'il existe une définition spécifique d'une classe trop grande. Cependant, j'utiliserais les directives suivantes pour déterminer si je devrais refactoriser ma classe ou redéfinir la portée de la classe:
En ce qui concerne 1, imaginez que vous définissez la taille de votre pare-brise, la taille des roues, le modèle d'ampoule de feu arrière et 100 autres détails dans votre classe
car
. Bien qu'ils soient tous "pertinents" pour la voiture, il est très difficile de retracer le comportement d'une telle classecar
. Et lorsqu'un jour, votre collègue modifie accidentellement (ou, dans certains cas, intentionnellement) la taille du pare-brise en fonction du modèle d'ampoule de feu arrière, il vous faut une éternité pour savoir pourquoi le pare-brise ne rentre plus dans votre voiture.En ce qui concerne 2, imaginez que vous essayez de définir tous les comportements qu'une voiture peut avoir dans la classe
car
, maiscar::open_roof()
ne sera disponible que pour les cabriolets etcar::open_rear_door()
ne s'applique pas à ces voitures à 2 portes. Bien que les cabriolets et les voitures à 2 portes soient par définition des «voitures», leur mise en œuvre dans la classecar
complique la classe. La classe devient plus difficile à utiliser et plus difficile à maintenir.Outre ces 2 lignes directrices, je suggérerais une définition claire de la portée de la classe. Une fois que vous avez défini la portée, implémentez la classe strictement en fonction de la définition. La plupart du temps, les gens obtiennent une classe "trop grande" en raison de la mauvaise définition de la portée ou en raison des fonctionnalités arbitraires ajoutées à la classe
la source
Dites ce dont vous avez besoin en 300 lignes
Remarque: cette règle empirique suppose que vous suivez l'approche «une classe par fichier» qui est en soi une bonne idée de maintenabilité
Ce n'est pas une règle absolue, mais si je vois plus de 200 lignes de code dans une classe, je me méfie. 300 lignes et sonneries d'avertissement s'allument. Lorsque j'ouvre le code de quelqu'un d'autre et que je trouve 2000 lignes, je sais que c'est du temps de refactorisation sans même lire la première page.
Cela se résume principalement à la maintenabilité. Si votre objet est si complexe que vous ne pouvez pas exprimer son comportement sur 300 lignes, il sera très difficile à comprendre pour quelqu'un d'autre.
Je constate que je plie cette règle dans le modèle -> ViewModel -> Afficher le monde dans lequel je crée un «objet comportement» pour une page d'interface utilisateur, car il faut souvent plus de 300 lignes pour décrire la série complète de comportements sur une feuille. Cependant, je suis encore nouveau dans ce modèle de programmation et je trouve de bons moyens de refactoriser / réutiliser les comportements entre les pages / modèles de vue, ce qui réduit progressivement la taille de mes ViewModels.
la source
Revenons en arrière:
En fait, c'est une pierre angulaire de la programmation, dont OO n'est qu'un paradigme.
Je vais laisser Antoine de Saint-Exupéry répondre à votre question (car je suis paresseux;)):
Plus la classe est simple, plus vous aurez raison, plus il sera facile de la tester complètement .
la source
Je suis avec l'OP sur la classification Feature Envy. Rien ne m'irrite plus que d'avoir un tas de classes avec un tas de méthodes qui fonctionnent sur les internes d'autres classes. OO vous suggère de modéliser les données et les opérations sur ces données. Cela ne signifie pas que vous placez les opérations dans un endroit différent des données! Il essaie de vous faire mettre les données et les méthodes ensemble .
Avec les modèles
car
etperson
si répandus, ce serait comme mettre le code pour faire la connexion électrique, ou même faire tourner le démarreur, dans laperson
classe. J'espère que ce serait manifestement mauvais pour tout programmeur expérimenté.Je n'ai pas vraiment de taille de classe ou de méthode idéale, mais je préfère garder mes méthodes et mes fonctions sur un seul écran afin que je puisse voir leur intégralité en un seul endroit. (J'ai Vim configuré pour ouvrir une fenêtre de 60 lignes, qui se trouve être à peu près le nombre de lignes sur une page imprimée.) Il y a des endroits où cela n'a pas beaucoup de sens, mais cela fonctionne pour moi. J'aime suivre la même ligne directrice pour les définitions de classe et essayer de garder mes fichiers sous quelques "pages" de long. Tout ce qui dépasse 300, 400 lignes commence à se sentir lourd (principalement parce que la classe a commencé à prendre trop de responsabilités directes).
la source
Lorsqu'une définition de classe a quelques centaines de méthodes, elle est trop grande.
la source
Je suis entièrement d'accord avec l'utilisation du principe de responsabilité unique pour les classes, mais si cela est suivi et donne lieu à de grandes classes, qu'il en soit ainsi. Le véritable objectif devrait être que les méthodes et les propriétés individuelles ne soient pas trop grandes, la complexité cyclomatique devrait être le guide là-bas et cela pourrait entraîner de nombreuses méthodes privées qui augmenteront la taille globale de la classe mais la laisseront lisible et testable à un niveau granulaire. Si le code reste lisible, la taille n'est pas pertinente.
la source
L'émission d'un bon de commande est souvent un processus compliqué qui implique plus qu'un simple bon de commande. Dans ce cas, conserver la logique métier dans une classe distincte et simplifier le bon de commande est probablement la bonne chose à faire. En général, cependant, vous avez raison - vous ne voulez pas de classes de "structure de données". Par exemple, votre
issue()
méthode de classe affaires "POManager" devrait probablement appeler laissue()
méthode du bon de commande. Le bon de commande peut ensuite définir son statut sur «Émis» et noter la date d'émission, tandis que le POManager peut ensuite envoyer une notification aux comptes fournisseurs pour leur faire savoir qu'ils attendent une facture, et une autre notification pour l'inventaire afin qu'ils sachent qu'il y a quelque chose qui arrive et la date prévue.Quiconque pousse des "règles" pour la taille de la méthode / classe n'a évidemment pas passé assez de temps dans le monde réel. Comme d'autres l'ont mentionné, c'est souvent un indicateur de refactorisation, mais parfois (en particulier dans le travail de type "traitement de données"), vous avez vraiment besoin d'écrire une classe avec une seule méthode qui couvre plus de 500 lignes. Ne vous y attardez pas.
la source
En termes de taille physique d'une classe, voir une grande classe est une indication d'une odeur de code, mais cela ne signifie pas nécessairement qu'il y a toujours des odeurs. (Habituellement, ils le sont cependant) Comme le disent les pirates des Pirates des Caraïbes, c'est plus ce que vous appelleriez des "lignes directrices" que des règles réelles. J'avais essayé de suivre strictement "pas plus de cent LOC dans une classe" et le résultat était BEAUCOUP de sur-ingénierie inutile.
Je commence généralement mes créations avec ça. Que fait cette classe dans le monde réel? Comment ma classe devrait-elle refléter cet objet comme indiqué dans ses exigences? Nous ajoutons un peu d'état ici, un champ là, une méthode pour travailler sur ces champs, ajoutez-en un peu plus et le tour est joué! Nous avons une classe ouvrière. Maintenant, remplissez les signatures avec les implémentations appropriées et nous sommes prêts à partir, du moins vous le pensez. Maintenant, nous avons une belle classe avec toutes les fonctionnalités dont nous avons besoin. Mais le problème est que cela ne tient pas compte du fonctionnement du monde réel. Et par là, je veux dire qu'il ne prend pas en compte, "CHANGE".
La raison pour laquelle les classes sont de préférence divisées en parties plus petites est qu'il est difficile de changer de grandes classes plus tard sans affecter la logique existante ou introduire un nouveau bogue ou deux involontairement ou simplement rendre tout difficile à réutiliser. C'est là que le refactoring, les modèles de conception, SOLID, etc. entrent en jeu et le résultat final est généralement une petite classe travaillant sur d'autres sous-classes plus petites et plus granulaires.
De plus, dans votre exemple, l'ajout de IssuePO, ApprovePO et Cancel PO dans la classe PurchaseOrder semble illogique. Les bons de commande ne sont pas émis, approuvés ou annulés eux-mêmes. Si PO doit avoir des méthodes, alors les méthodes doivent travailler sur son état et non sur la classe dans son ensemble. Ce ne sont vraiment que des classes de données / enregistrements sur lesquelles d'autres classes travaillent.
la source
Assez grand pour contenir toute la logique nécessaire pour effectuer son travail.
Assez petit pour être maintenable et suivre le principe de responsabilité unique .
la source