web-dev-qa-db-fra.com

Quelle est la durée idéale d'une méthode pour vous?

Dans la programmation orientée objet, il n'y a bien sûr pas de règle exacte sur la longueur maximale d'une méthode, mais j'ai quand même trouvé ces deux citations quelque peu contradictoires, donc j'aimerais entendre ce que vous en pensez.

Dans Clean Code: A Handbook of Agile Software Craftsmanship , Robert Martin dit:

La première règle des fonctions est qu'elles doivent être petites. La deuxième règle des fonctions est qu'elles doivent être plus petites que cela. Les fonctions ne doivent pas comporter 100 lignes. Les fonctions ne devraient presque jamais comporter 20 lignes.

et il donne un exemple de Java code qu'il voit de Kent Beck:

Chaque fonction de son programme ne comptait que deux, trois ou quatre lignes. Chacun était d'une évidence transparente. Chacun a raconté une histoire. Et chacun vous a conduit au suivant dans un ordre convaincant. Voilà à quel point vos fonctions devraient être courtes!

Cela semble génial, mais d'un autre côté, dans Code complet , Steve McConnell dit quelque chose de très différent:

La routine devrait pouvoir croître organiquement jusqu'à 100-200 lignes, des décennies de preuves indiquent que les routines d'une telle longueur ne sont plus sujettes aux erreurs que les routines plus courtes.

Et il fait référence à une étude qui dit que les routines de 65 lignes ou plus longues sont moins chères à développer.

Donc, même s'il existe des opinions divergentes sur la question, existe-t-il une pratique exemplaire pour vous?

128
iPhoneDeveloper

Les fonctions doivent normalement être courtes, entre 5 et 15 lignes est ma "règle générale" personnelle lors du codage en Java ou C #. C'est une bonne taille pour plusieurs raisons:

  • Il s'adapte facilement à votre écran sans défilement
  • Il s'agit de la taille conceptuelle que vous pouvez tenir dans votre tête
  • Il est suffisamment significatif pour nécessiter une fonction à part entière (en tant que bloc de logique autonome et significatif)
  • Une fonction inférieure à 5 lignes indique que vous divisez peut-être trop le code (ce qui rend la lecture/compréhension plus difficile si vous devez naviguer entre les fonctions). Soit cela, soit vous oubliez vos cas spéciaux/gestion des erreurs!

Mais je ne pense pas qu'il soit utile de définir une règle absolue, car il y aura toujours des exceptions/raisons valables pour s'écarter de la règle:

  • Une fonction d'accesseur sur une ligne qui effectue une conversion de type est clairement acceptable dans certaines situations.
  • Il existe des fonctions très courtes mais utiles (par exemple, l'échange comme mentionné par l'utilisateur inconnu) qui nécessitent clairement moins de 5 lignes. Pas grave, quelques fonctions de 3 lignes ne font pas de mal à votre base de code.
  • Une fonction de 100 lignes qui est une seule instruction switch large peut être acceptable si elle est extrêmement claire ce qui est fait. Ce code peut être conceptuellement très simple même s'il nécessite beaucoup de lignes pour décrire les différents cas. Parfois, il est suggéré que cela devrait être refactorisé en classes distinctes et implémenté en utilisant l'héritage/polymorphisme, mais à mon humble avis, cela prend OOP trop loin - je préfère n'avoir qu'une seule instruction de commutation à 40 voies plutôt que 40 nouvelles classes à gérer, en plus d'une instruction de commutation à 40 voies pour les créer.
  • Une fonction complexe peut avoir beaucoup de variables d'état qui pourraient devenir très compliquées si elles étaient passées entre différentes fonctions en tant que paramètres. Dans ce cas, vous pouvez raisonnablement faire valoir que le code est plus simple et plus facile à suivre si vous conservez tout dans une seule grande fonction (bien que, comme Mark le souligne à juste titre, cela pourrait également être un candidat pour devenir une classe pour encapsuler à la fois la logique et état).
  • Parfois, des fonctions plus petites ou plus grandes ont des avantages en termes de performances (peut-être pour des raisons d'inline ou JIT comme Frank le mentionne). Cela dépend fortement de l'implémentation, mais cela peut faire une différence - assurez-vous de comparer!

Donc, fondamentalement, utilisez votre bon sens , respectez les petites tailles de fonction dans la plupart des cas, mais ne soyez pas dogmatique à ce sujet si vous ont une bonne raison de faire une fonction inhabituellement grande.

119
mikera

Bien que je sois d'accord avec les commentaires des autres quand ils ont dit qu'il n'y avait pas de règle stricte sur le bon numéro LOC, je parie que si nous regardons en arrière les projets que nous avons examinés dans le passé et identifions chaque fonction ci-dessus, disons 150 lignes de code, je '' Je suppose que nous arriverions à un consensus sur le fait que 9 sur 10 de ces fonctions cassent SRP (et très probablement OCP également), ont trop de variables locales, trop de flux de contrôle et sont généralement difficiles à lire et à maintenir.

Ainsi, bien que LOC ne soit pas un indicateur direct d'un mauvais code, c'est certainement un indicateur indirect décent que certaines fonctions pourraient être mieux écrites.

Dans mon équipe, je suis tombé dans la position de leader et pour quelque raison que ce soit, les gens semblent m'écouter. Ce que j'ai généralement décidé, c'est de dire à l'équipe que, même s'il n'y a pas de limite absolue, toute fonction de plus de 50 lignes de code devrait au moins déclencher un drapeau rouge lors de la révision du code, afin que nous l'examinions à nouveau et le réévaluions. pour la complexité et les violations SRP/OCP. Après ce deuxième regard, nous pourrions le laisser tranquille ou le changer, mais au moins cela fait réfléchir les gens à ces choses.

32
DXM

Je suis entré dans un projet qui ne s'est pas soucié des directives de codage. Quand je regarde dans le code, je trouve parfois des classes avec plus de 6000 lignes de code et moins de 10 méthodes. Il s'agit d'un scénario d'horreur lorsque vous devez corriger des bogues.

Une règle générale de la taille maximale d'une méthode n'est parfois pas aussi bonne. J'aime la règle de Robert C. Martin (oncle Bob): "Les méthodes doivent être petites, plus petites que petites". J'essaie d'utiliser cette règle tout le temps. J'essaie de garder mes méthodes simples et petites en précisant que ma méthode ne fait qu'une chose et rien de plus.

22
Smokefoot

Il ne s'agit pas de nombre de lignes, il s'agit de SRP. Selon ce principe, votre méthode doit faire une et une seule chose.

Si votre méthode fait ceci ET ceci ET ceci OR que => cela fait probablement trop. Essayez de regarder cette méthode et d'analyser: "ici, j'obtiens ces données, les trie et j'obtiens des éléments J'ai besoin de "et" ici je traite ces éléments "et" ici je les combine enfin pour obtenir le résultat ". Ces" blocs "devraient être refactorisés vers d'autres méthodes.

Si vous suivez simplement SRP, la plupart de votre méthode sera petite et avec une intention claire.

Il n'est pas correct de dire "cette méthode est> 20 lignes donc c'est faux". Il peut être ne indication que quelque chose peut-être mal avec cette méthode, pas plus.

Vous pouvez avoir un commutateur de 400 lignes dans une méthode (cela arrive souvent dans les télécommunications), et c'est toujours la responsabilité unique et c'est parfaitement OK.

11
Alexey

Cela dépend , sérieusement, il n'y a vraiment pas de réponse solide à cette question car la langue avec laquelle vous travaillez est importante, les cinq à quinzième lignes mentionnées - dans cette réponse pourrait fonctionner pour C # ou Java, mais dans d'autres langages, cela ne vous donne pas beaucoup de travail. De même, selon le domaine dans lequel vous travaillez, vous pourriez vous retrouver à écrire des valeurs de définition de code dans une grande structure de données. Avec certaines structures de données, vous pourriez avoir des dizaines d'éléments que vous devez définir, devez-vous répartir les choses en fonctions séparées simplement parce que votre fonction est longue?

Comme d'autres l'ont fait remarquer, la meilleure règle de base est qu'une fonction doit être une seule entité logique qui gère une seule tâche. Si vous essayez d'appliquer des règles draconiennes qui disent que les fonctions ne peuvent pas être plus longues que n lignes et que vous rendez cette valeur trop petite, votre code deviendra plus difficile à lire à mesure que les développeurs essaieront d'utiliser des astuces sophistiquées pour contourner la règle. De même, si vous le définissez trop haut, ce sera un non-problème et peut conduire à un mauvais code par paresse. Votre meilleur pari est de simplement effectuer des révisions de code pour vous assurer que les fonctions gèrent une seule tâche et en rester là.

9
rjzii

Je pense qu'un problème ici est que la longueur d'une fonction ne dit rien sur sa complexité. LOC (lignes de code) sont un mauvais instrument pour mesurer quoi que ce soit.

Une méthode ne doit pas être trop complexe, mais il existe des scénarios où une méthode longue peut être facilement maintenue. Notez que l'exemple suivant ne dit pas qu'il ne pourrait pas être divisé en méthodes, mais simplement que les méthodes ne changeraient pas la maintenabilité.

par exemple, un gestionnaire de données entrantes peut avoir une grande instruction switch puis un code simple par cas. J'ai un tel code - gérer les données entrantes à partir d'un flux. 70 (!) Gestionnaires codés numériquement. Maintenant, on dira "utiliser des constantes" - oui, sauf que l'API ne les fournit pas et j'aime rester proche de "source" ici. Méthodes? Bien sûr, malheureusement, tous traitent des données provenant des deux mêmes énormes structures. Aucun avantage à les séparer sauf peut-être avoir plus de méthodes (lisibilité). Le code n'est intrinsèquement pas complexe - un commutateur, selon un champ. Ensuite, chaque cas a un bloc qui analyse x éléments de données et les publie. Aucun cauchemar de maintenance. Il y en a une qui répète "if condition qui détermine si un champ a des données (pField = pFields [x], si pField-> IsSet () {blabla}) - la même chose pour chaque champ ...

Remplacez cela par une routine beaucoup plus petite contenant une boucle imbriquée et de nombreuses instructions de commutation réelles et une méthode énorme peut être plus facile à maintenir qu'une plus petite.

Donc, désolé, LOC n'est pas une bonne mesure pour commencer. Si quelque chose, alors des points de complexité/décision devraient être utilisés.

8
TomTom

Je vais juste ajouter une autre citation.

Les programmes doivent être écrits pour que les gens puissent les lire, et accessoirement pour les machines à exécuter

- Harold Abelson

Il est très improbable que les fonctions qui atteignent 100-200 suivent cette règle

8
Pete

Je suis dans cette raquette folle, d'une manière ou d'une autre, depuis 1970.

Pendant tout ce temps, à deux exceptions près, je n'aurai JAMAIS vu de "routine" bien conçue (méthode, procédure, fonction, sous-programme, etc.) qui DOIT être composée de plusieurs pages imprimées ( environ 60 lignes) de long. La grande majorité d'entre eux étaient assez courts, de l'ordre de 10 à 20 lignes.

J'ai cependant vu BEAUCOUP de code de "courant de conscience", écrit par des gens qui n'ont apparemment jamais entendu parler de modularisation.

Les deux exceptions sont des cas très particuliers. L'un est en fait une classe de cas d'exception, que je regroupe: de grands automates à états finis, implémentés comme de grosses instructions de commutation laides, généralement parce qu'il n'y a pas de moyen plus propre de les implémenter. Ces éléments apparaissent généralement dans l'équipement de test automatisé, analysant les journaux de données de l'appareil testé.

L'autre était la routine de torpille à photons du jeu STARTRK Matuszek-Reynolds-McGehearty-Cohen, écrite en CDC 6600 FORTRAN IV. Il devait analyser la ligne de commande, puis simuler le vol de chaque torpille, avec des perturbations, vérifier l'interaction entre la torpille et chaque type de chose qu'elle pouvait frapper, et oh au fait simuler la récursion pour faire une connectivité à 8 voies sur des chaînes de novae de torpiller une étoile qui était à côté d'autres étoiles.

6
John R. Strohm

Si je trouve une méthode longue, je pourrais parier que cette méthode n'est pas testée correctement ou la plupart du temps elle n'a pas du tout de test unitaire. Si vous commencez à faire TDD, vous ne construirez jamais de méthodes de 100 lignes avec 25 responsabilités différentes et 5 boucles imbriquées. Les tests vous obligent à remanier constamment votre gâchis et à écrire le code propre de l'oncle Bob.

5
Sergii Shevchyk

Il n'y a pas de règles absolues sur la longueur de la méthode, mais les règles suivantes ont été utiles:

  1. Le but principal de la fonction est de trouver la valeur de retour. Il n'y a pas d'autre raison à son existence. Une fois cette raison remplie, aucun autre code ne doit y être inséré. Cela réduit nécessairement les fonctions. L'appel à d'autres fonctions ne doit être effectué que si cela facilite la recherche de la valeur de retour.
  2. D'un autre côté, les interfaces doivent être petites. Cela signifie que vous avez un grand nombre de classes ou que vous avez de grandes fonctions - l'une des deux se produira une fois que vous commencerez à avoir suffisamment de code pour faire quelque chose d'important. Les grands programmes peuvent avoir les deux.
2
tp1

Les auteurs entendent-ils la même chose par "fonction" et "routine"? Typiquement, quand je dis "fonction", je veux dire un sous-programme/opération qui renvoie une valeur et une "procédure" pour celle qui ne le fait pas (et dont l'appel devient une seule instruction). Ce n'est pas une distinction courante dans SE dans le monde réel, mais je l'ai vu dans les textes utilisateur.

Quoi qu'il en soit, il n'y a pas de bonne réponse à cela. La préférence pour l'un ou l'autre (s'il y a une préférence) est quelque chose que j'attendrais à être très différent entre les langues, les projets et les organisations; tout comme pour toutes les conventions de code.

La seule chose que j'ajouterais est que l'affirmation "les opérations longues ne sont pas plus sujettes aux erreurs que les opérations courtes" n'est pas strictement vraie. En plus du fait que plus de code équivaut à plus d'espace d'erreur potentiel, il est évident que la décomposition du code en segments rendra les erreurs plus faciles à éviter et à localiser. Sinon, il n'y aurait aucune raison de casser le code en morceaux, sauf la répétition. Mais cela n'est peut-être vrai que si lesdits segments sont suffisamment documentés pour que vous puissiez déterminer les résultats d'un appel d'opération sans lire ou tracer le code réel (conception par contrat basée sur des spécifications plutôt que sur une dépendance concrète entre les zones de code).

En outre, si vous souhaitez que les opérations plus longues fonctionnent correctement, vous souhaiterez peut-être adopter des conventions de code plus strictes pour les prendre en charge. Lancer une instruction de retour au milieu d'une opération peut être bien pour une opération courte, mais dans des opérations plus longues, cela peut créer une grande section de code qui est conditionnelle mais pas évidemment conditionnelle à une lecture rapide (juste pour un exemple).

Je pense donc que le style le moins susceptible d'être un cauchemar rempli de bogues dépendra en grande partie des conventions auxquelles vous adhérez pour le reste de votre code. :)

2
Trixie Wolf

À mon humble avis, vous ne devriez pas avoir à utiliser la barre de défilement pour lire votre fonction. Dès que vous devez déplacer la barre de défilement, il faut un peu plus de temps pour comprendre comment fonctionne la fonction.

En conséquence, cela dépend de l'environnement de programmation habituel de votre travail d'équipe (résolution d'écran, éditeur, taille de police, etc ...). Dans les années 80, c'était 25 lignes et 80 colonnes. Maintenant, sur mon éditeur, j'affiche près de 50 lignes. Le nombre de colonnes que j'affiche n'a pas changé depuis que j'ai divisé mon écran en deux pour afficher deux fichiers à la fois.

En bref, cela dépend de la configuration de vos collègues.

1
Jérôme Pouiller

Je pense que réponse de TomTom est venu près de ce que je ressens à ce sujet.

Je me retrouve de plus en plus sur la complexité cyclomatique plutôt que sur les lignes.

Je ne vise normalement pas plus d'une structure de contrôle par méthode, à l'exception du nombre de boucles nécessaires pour gérer un tableau multidimensionnel.

Je me retrouve parfois à mettre des ifs à une ligne dans les boîtiers de commutation, car pour une raison quelconque, il s'agit généralement de cas où le fractionner gêne plutôt qu'il n'aide.

Notez que je ne compte pas la logique de garde contre cette limite.

1
Loren Pechtel