Quelle taille est acceptable pour une classe?

29

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 PurchaseOrderclasse, 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 PurchaseOrderclasse 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 PurchaseOrderclasse devrait avoir issuePO, approvePOet les cancelPOmé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?

Miguel Veloso
la source
17
Il n'y a aucune raison de coder le nom de type dans le nom de la méthode. Autrement dit , si vous avez PurchaseOrder, vous pouvez simplement nommer vos méthodes issue, approveet cancel. Le POsuffixe n'ajoute qu'une valeur négative.
dash-tom-bang
2
Tant que vous restez loin des méthodes de trou noir , vous devriez être OK. :)
Mark C
1
Avec ma résolution d'écran actuelle, je dirais 145 caractères.
DavRob60
Merci à tous pour vos commentaires, ils m'ont vraiment aidé avec ce problème. Si ce site avait permis, j'aurais aussi choisi la réponse de TMN et de Lazare, mais il n'en autorise qu'une, alors c'est allé à Craig. Meilleures salutations.
Miguel Veloso

Réponses:

27

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à.

Craig
la source
1
Craig, je n'étais pas au courant du principe de responsabilité unique et je viens de le lire. La première chose qui m'est venue à l'esprit était: quelle est la portée d'une responsabilité? pour un bon de commande, je dirais qu'il gère son état, et c'est la raison des méthodes issuePO, approvePO et cancelPO, mais inclut également l'interaction avec la classe PurchaseOrderItem qui gère l'état de l'élément de bon de commande. Je ne sais donc pas si cette approche est compatible avec le PÉR. que dirais-tu?
Miguel Veloso
1
+1 pour une réponse très agréable et succincte. En réalité, il ne peut pas y avoir de mesure définitive de la taille d'une classe. L'application du SRP garantit que la classe est uniquement aussi grande qu'elle doit l' être.
S.Robins
1
@MiguelVeloso - L'approbation d'un bon de commande a probablement une logique et des règles différentes qui lui sont associées que l'émission d'un bon de commande. Si c'est le cas, il est logique de séparer les responsabilités en un POApprover et un POIssuer. L'idée est que si les règles changent pour l'une, pourquoi voudriez-vous jouer avec la classe qui contient l'autre.
Matthew Flynn
12

À 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.

Mike42
la source
1
D'un autre côté, une grande taille indique que ces méthodes ne sont probablement pas pertinentes pour la classe en question.
Billy ONeal
5
@Billy: Je dirais que les grandes classes sont une odeur de code, mais elles ne sont pas automatiquement mauvaises.
David Thornley du
@David: C'est pourquoi il y a le mot "probablement" là-dedans - c'est important :)
Billy ONeal
Je ne serais pas d'accord ... Je travaille sur un projet qui a une convention de dénomination assez cohérente et les choses sont bien présentées ... mais j'ai encore une classe qui fait 50k lignes de code. C'est juste trop gros :)
Jay
2
Cette réponse montre exactement comment se déroule la "route de l'enfer" - si l'on ne restreint pas les responsabilités d'une classe, de nombreuses variables, fonctions et méthodes deviendront pertinentes pour la classe - et cela conduit facilement à en avoir trop .
Doc Brown
7

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 carest trop grande, c'est probablement une indication qu'elle doit être divisée en classe engine, classe doors et classe windshields, etc.

Billy ONeal
la source
8
Et n'oublions pas qu'une voiture doit implémenter l'interface véhicule. :-)
Chris
7

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:

  1. Existe-t-il de nombreuses variables indépendantes les unes des autres? (Ma tolérance personnelle est d'environ 10 à 20 dans la plupart des cas)
  2. Existe-t-il de nombreuses méthodes conçues pour les caissons d'angle? (Ma tolérance personnelle est ... eh bien, cela dépend fortement de mon humeur, je suppose)

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 classe car. 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, mais car::open_roof()ne sera disponible que pour les cabriolets et car::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 classe carcomplique 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

YYC
la source
7

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.

Jay Beavers
la source
Ma ligne directrice personnelle est également d'environ 300 lignes. +1
Marcie
Je pense que le PO recherche une heuristique, et celle-ci est bonne.
neontapir
Merci, il est utile d'utiliser des chiffres précis comme ligne directrice.
Will Sheppard du
6

Revenons en arrière:

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?

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;)):

La perfection est atteinte, non pas quand il n'y a plus rien à ajouter, mais quand il n'y a plus rien à emporter.

Plus la classe est simple, plus vous aurez raison, plus il sera facile de la tester complètement .

Matthieu M.
la source
3

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 caret personsi répandus, ce serait comme mettre le code pour faire la connexion électrique, ou même faire tourner le démarreur, dans la personclasse. 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).

dash-tom-bang
la source
+1 - bonnes règles de base mais pas autoritaires à ce sujet. Je dirais cependant que ce n'est pas parce qu'une classe est divisée que les différentes classes doivent toucher les membres des autres.
Billy ONeal
Je ne pense pas que différentes classes devraient jamais toucher les parties intimes de l'autre. Bien que l'accès «ami» soit pratique pour mettre quelque chose en marche, (pour moi) c'est un tremplin vers un meilleur design. En général, j'évite également les accesseurs, car dépendre des internes d'une autre classe est une autre odeur de code .
dash-tom-bang du
1

Lorsqu'une définition de classe a quelques centaines de méthodes, elle est trop grande.

C Johnson
la source
1

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.

Lazare
la source
1

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 la issue()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.

TMN
la source
Je suppose que le POManager serait le "contrôleur" et PurchaseOrder serait le "modèle" dans le modèle MVC, non?
Miguel Veloso du
0

J'ai trouvé plusieurs auteurs qui proposent des recettes telles que «une classe devrait tenir sur une seule page»

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.

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".

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.

Jonn
la source
0

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 .

RMalke
la source