web-dev-qa-db-fra.com

Quand une fonction est-elle trop longue?

35 lignes, 55 lignes, 100 lignes, 300 lignes? Quand devriez-vous commencer à le séparer? Je demande parce que j'ai une fonction avec 60 lignes (y compris les commentaires) et que je pensais la séparer.

long_function(){ ... }

dans:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

Les fonctions ne vont pas être utilisées en dehors de la fonction longue, faire des fonctions plus petites signifie plus d'appels de fonctions, etc.

Quand sépareriez-vous une fonction en plus petites? Pourquoi?

  1. Les méthodes ne devraient faire qu'une seule chose logique (penser à la fonctionnalité)
  2. Vous devriez pouvoir expliquer la méthode en une seule phrase
  3. Il devrait s'adapter à la hauteur de votre écran
  4. Évitez les frais généraux inutiles (commentaires qui soulignent l'évidence ...)
  5. Le test unitaire est plus facile pour les petites fonctions logiques
  6. Vérifiez si une partie de la fonction peut être réutilisée par d'autres classes ou méthodes
  7. Évitez le couplage inter-classe excessif
  8. Évitez les structures de contrôle profondément imbriquées

Merci à tous pour les réponses, éditez la liste et votez pour la bonne réponse je vais choisir celle-ci;)

Je refactorise maintenant avec ces idées à l'esprit :)

124
Movaxes

Il n'y a pas vraiment de règles strictes pour cela. En général, j'aime mes méthodes pour "faire une seule chose". Donc, s'il s'agit de récupérer des données, puis de faire quelque chose avec ces données, puis de les écrire sur le disque, je diviserais la saisie et l'écriture en méthodes distinctes, donc ma méthode "principale" ne contient que le "faire quelque chose".

Cependant, "faire quelque chose" pourrait encore représenter quelques lignes, donc je ne suis pas sûr qu'un certain nombre de lignes soit la bonne mesure à utiliser :)

Edit: Ceci est une seule ligne de code que j'ai postée autour du travail la semaine dernière (pour prouver un point .. ce n'est pas quelque chose dont je prends l'habitude :)) - Je ne voudrais certainement pas 50-60 de ces mauvais garçons dans ma méthode :RÉ

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();
74
Steven Robbins

Voici une liste de drapeaux rouges (sans ordre particulier) qui pourraient indiquer qu'une fonction est trop longue:

  1. Structures de contrôle profondément imbriquées: par ex. for-loops 3 niveaux de profondeur ou même seulement 2 niveaux de profondeur avec des instructions if imbriquées qui ont des conditions complexes.

  2. Trop de paramètres définissant l'état : Par paramètre définissant l'état , je veux dire un paramètre de fonction qui garantit un chemin d'exécution particulier à travers la fonction. Obtenez trop de ce type de paramètres et vous obtenez une explosion combinatoire de chemins d'exécution (cela se produit généralement en tandem avec le n ° 1).

  3. Logique dupliquée dans d'autres méthodes: une mauvaise réutilisation du code contribue énormément au code procédural monolithique. Un grand nombre de ces duplications logiques peuvent être très subtiles, mais une fois remodelées, le résultat final peut être un design beaucoup plus élégant.

  4. couplage inter-classes excessif: ce manque d'encapsulation appropriée a pour conséquence que les fonctions se préoccupent des caractéristiques intimes des autres classes, d'où leur allongement.

  5. surcharge inutile: commentaires qui soulignent les classes évidentes et profondément imbriquées, les getters et les setters superflus pour les variables de classe imbriquées privées, et les noms de fonction/variable inhabituellement longs peuvent tous créer du bruit syntaxique au sein des fonctions connexes qui augmenteront finalement leur longueur.

  6. Votre affichage massif de niveau développeur n'est pas assez grand pour l'afficher: En fait, les affichages d'aujourd'hui sont assez grands pour qu'une fonction qui soit n'importe où près de sa hauteur soit probablement beaucoup trop longue. Mais, si c'est plus grand, c'est un pistolet fumant que quelque chose ne va pas.

  7. Vous ne pouvez pas déterminer immédiatement le but de la fonction: De plus, une fois que vous avez réellement déterminé son but, si vous ne pouvez pas résumer cet objectif en une seule phrase ou avoir un énorme mal de tête, cela devrait être un indice.

En conclusion, les fonctions monolithiques peuvent avoir des conséquences d'une grande portée et sont souvent le symptôme de défauts de conception majeurs. Chaque fois que je rencontre du code qui est une joie absolue à lire, son élégance est immédiatement apparente. Et devinez quoi: les fonctions sont souvent très de courte durée.

201
Ryan Delucchi

Je pense qu'il y a une énorme mise en garde au mantra "ne faire qu'une seule chose" sur cette page. Parfois, faire une chose jongle avec beaucoup de variables. Ne divisez pas une fonction longue en un tas de fonctions plus petites si les fonctions plus petites finissent par avoir de longues listes de paramètres. Faire cela transforme simplement une seule fonction en un ensemble de fonctions hautement couplées sans réelle valeur individuelle.

28
jmucchiello

Une fonction ne doit faire qu'une seule chose. Si vous faites beaucoup de petites choses dans une fonction, faites de chaque petite chose une fonction et appelez ces fonctions à partir de la fonction longue.

Ce que vous voulez vraiment ne pas c'est copier et coller toutes les 10 lignes de votre fonction longue dans des fonctions courtes (comme le suggère votre exemple).

19
Jeremy Ruten

Je suis d'accord qu'une fonction ne devrait faire qu'une seule chose, mais à quel niveau est cette seule chose.

Si vos 60 lignes accomplissent une chose (du point de vue de vos programmes) et que les éléments qui composent ces 60 lignes ne seront pas utilisés par autre chose, alors 60 lignes sont très bien.

Il n'y a aucun avantage réel à le briser, à moins que vous ne puissiez le briser en morceaux de béton autonomes. La métrique à utiliser est la fonctionnalité et non les lignes de code.

J'ai travaillé sur de nombreux programmes où les auteurs ont pris la seule chose à un niveau extrême et tout ce qu'il a fini par faire était de faire ressembler quelqu'un à une grenade à une fonction/méthode et à la faire exploser en des dizaines de morceaux non connectés qui sont dur à suivre.

Lorsque vous retirez des éléments de cette fonction, vous devez également vous demander si vous ajouterez des frais généraux inutiles et éviter de transmettre de grandes quantités de données.

Je crois que le point clé est de rechercher la réutilisabilité dans cette longue fonction et de retirer ces pièces. Il vous reste la fonction, qu'elle soit de 10, 20 ou 60 lignes.

16
bruceatk

60 lignes est grande mais pas trop longue pour une fonction. S'il tient sur un seul écran dans un éditeur, vous pouvez tout voir en même temps. Cela dépend vraiment de ce que font les fonctions.

Pourquoi je peux casser une fonction:

  • C'est trop long
  • Il rend le code plus facile à gérer en le décomposant et en utilisant des noms significatifs pour la nouvelle fonction
  • La fonction n'est pas cohérente
  • Certaines parties de la fonction sont utiles en elles-mêmes.
  • Quand il est difficile de trouver un nom significatif pour la fonction (cela fait probablement trop)
11
dajorad

Taille environ la taille de votre écran (alors allez chercher un grand écran panoramique pivotant et tournez-le) ... :-)

Blague à part, une chose logique par fonction.

Et la chose positive est que le test unitaire est vraiment beaucoup plus facile à faire avec de petites fonctions logiques qui font 1 chose. Les grosses fonctions qui font beaucoup de choses sont plus difficiles à vérifier!

/ Johan

6
Johan

Règle générale: si une fonction contient des blocs de code qui font quelque chose, quelque peu détaché du reste du code, placez-la dans une fonction distincte. Exemple:

function build_address_list_for_Zip($Zip) {

    $query = "SELECT * FROM ADDRESS WHERE Zip = $Zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a Nice looking list of
    // addresses for the user
    return $html_content;
}

beaucoup plus agréable:

function fetch_addresses_for_Zip($Zip) {
    $query = "SELECT * FROM ADDRESS WHERE Zip = $Zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_Zip($Zip) {

    $addresses = fetch_addresses_for_Zip($Zip);

    // now create a Nice looking list of
    // addresses for the user
    return $html_content;
}

Cette approche a deux avantages:

  1. Chaque fois que vous devez récupérer des adresses pour un certain code postal, vous pouvez utiliser la fonction facilement disponible.

  2. Lorsque vous avez besoin de relire la fonction build_address_list_for_Zip(), vous savez ce que le premier bloc de code va faire (il récupère les adresses d'un certain code postal, du moins c'est ce que vous pouvez déduire du nom de la fonction). Si vous aviez laissé le code de requête en ligne, vous devez d'abord analyser ce code.

[D'un autre côté (je nierai que je vous l'ai dit, même sous la torture): Si vous lisez beaucoup sur PHP optimisation, vous pourriez avoir l'idée de garder le nombre de fonctions aussi petit un possible, car les appels de fonction sont très, très chers en PHP. Je ne sais pas car je n'ai jamais fait de benchmarks. Si tel est le cas, il serait probablement préférable de ne pas suivre les réponses à votre question si votre application est très "sensible aux performances" ;-)]

6
EricSchaefer

Jetez un œil à la cyclomatique de McCabe, dans laquelle il décompose son code en un graphique où, "Chaque nœud du graphique correspond à un bloc de code dans le programme où le flux est séquentiel et les arcs correspondent aux branches prises dans le programme. "

Imaginez maintenant que votre code n'a pas de fonctions/méthodes; c'est juste un énorme étalement de code sous la forme d'un graphique.

Vous souhaitez décomposer cet étalement en méthodes. Considérez que, lorsque vous le faites, il y aura un certain nombre de blocs dans chaque méthode. Un seul bloc de chaque méthode sera visible pour toutes les autres méthodes: le premier bloc (nous supposons que vous ne pourrez sauter dans une méthode qu'à un seul point: le premier bloc). Tous les autres blocs de chaque méthode seront des informations cachées dans cette méthode, mais chaque bloc d'une méthode peut potentiellement passer à n'importe quel autre bloc de cette méthode.

Pour déterminer la taille de vos méthodes en termes de nombre de blocs par méthode, une question que vous pourriez vous poser est la suivante: combien de méthodes dois-je avoir pour minimiser le nombre potentiel maximal de dépendances (MPE) entre tous les blocs?

Cette réponse est donnée par une équation. Si r est le nombre de méthodes qui minimise l'EMT du système, et n est le nombre de blocs dans le système, alors l'équation est: r = sqrt (n)

Et on peut montrer que cela donne le nombre de blocs par méthode à être, également, sqrt (n).

6
Ed Kirwan

Mon heuristique personnelle est que c'est trop long si je ne peux pas voir le tout sans défiler.

6
Ferruccio

Gardez à l'esprit que vous pouvez finir par refactoriser juste pour le re-factoriser, ce qui pourrait rendre le code plus illisible qu'il ne l'était en premier lieu.

Un ancien collègue à moi avait une règle bizarre qu'une fonction/méthode ne doit contenir que 4 lignes de code! Il a essayé de s'y tenir si rigidement que ses noms de méthode sont souvent devenus répétitifs et dénués de sens, et les appels sont devenus profondément imbriqués et déroutants.

Donc mon propre mantra est devenu: si vous ne pouvez pas penser à un nom de fonction/méthode décent pour le morceau de code que vous refactorisez, ne vous embêtez pas.

4
Saltmeister

La principale raison pour laquelle je casse généralement une fonction est soit parce que des morceaux de celle-ci sont également des ingrédients d'une autre fonction proche que j'écris, de sorte que les parties communes sont prises en compte. De plus, s'il utilise un grand nombre de champs ou de propriétés d'une autre classe, il y a de fortes chances que le bloc concerné puisse être retiré en gros et si possible déplacé dans l'autre classe.

Si vous avez un bloc de code avec un commentaire en haut, envisagez de le retirer dans une fonction, avec les noms de fonction et d'argument illustrant son objectif, et de réserver le commentaire pour la justification du code.

Êtes-vous sûr qu'il n'y a pas de pièces qui pourraient être utiles ailleurs? De quelle sorte de fonction s'agit-il?

2
Jeffrey Hantin

À mon avis, la réponse est: quand cela fait trop de choses. Votre fonction doit effectuer uniquement les actions que vous attendez du nom de la fonction elle-même. Une autre chose à considérer est si vous souhaitez réutiliser certaines parties de vos fonctions dans d'autres; dans ce cas, il pourrait être utile de le diviser.

2
Pierpaolo

Je brise généralement les fonctions par la nécessité de placer des commentaires décrivant le prochain bloc de code. Ce qui était auparavant dans les commentaires va maintenant dans le nouveau nom de la fonction. Ce n'est pas une règle stricte, mais (pour moi) une belle règle d'or. J'aime le code qui parle pour lui-même mieux que celui qui a besoin de commentaires (car j'ai appris que les commentaires mentent généralement)

2
Olaf Kock

J'utilise normalement une approche pilotée par les tests pour écrire du code. Dans cette approche, la taille de la fonction est souvent liée à la granularité de vos tests.

Si votre test est suffisamment concentré, il vous amènera à écrire une petite fonction focalisée pour réussir le test.

Cela fonctionne également dans l'autre sens. Les fonctions doivent être suffisamment petites pour pouvoir être testées efficacement. Ainsi, lorsque je travaille avec du code hérité, je trouve souvent que je décompose des fonctions plus importantes afin de tester leurs différentes parties.

Je me demande généralement "quelle est la responsabilité de cette fonction" et si je ne peux pas énoncer la responsabilité dans une phrase claire et concise, puis traduire cela en un petit test ciblé, je me demande si la fonction est trop grande.

2
lexicalscope

C'est en partie une question de goût, mais comment je le détermine, j'essaie de garder mes fonctions à peu près aussi longtemps que cela ira sur mon écran à la fois (au maximum). La raison étant qu'il est plus facile de comprendre ce qui se passe si vous pouvez voir le tout à la fois.

Quand je code, c'est un mélange d'écriture de longues fonctions, puis de refactorisation pour extraire des bits qui pourraient être réutilisés par d'autres fonctions - et - écrire de petites fonctions qui effectuent des tâches discrètes au fur et à mesure.

Je ne sais pas s'il y a une bonne ou une mauvaise réponse à cela (par exemple, vous pouvez vous contenter de 67 lignes comme maximum, mais il peut y avoir des moments où il est logique d'ajouter quelques autres).

1
Andrew Hedges

Il y a eu des recherches approfondies sur ce sujet même, si vous voulez le moins de bugs, votre code ne devrait pas être trop long. Mais cela ne devrait pas non plus être trop court.

Je ne suis pas d'accord qu'une méthode devrait tenir sur votre écran en une seule, mais si vous faites défiler plus d'une page, la méthode est trop longue.

Voir La taille de classe optimale pour les logiciels orientés objet pour plus de détails.

1
Ian Hickman

J'ai déjà écrit 500 fonctions de ligne auparavant, mais ce n'étaient que de grandes instructions de commutateur pour décoder et répondre aux messages. Lorsque le code d'un seul message est devenu plus complexe qu'un seul si-alors-autre, je l'ai extrait.

Essentiellement, bien que la fonction soit de 500 lignes, les régions maintenues de façon indépendante avaient en moyenne 5 lignes.

1
Joshua

S'il a plus de trois branches, cela signifie généralement qu'une fonction ou une méthode doit être séparée, pour encapsuler la logique de branchement dans différentes méthodes.

Chaque boucle for, instruction if, etc. n'est alors pas considérée comme une branche dans la méthode d'appel.

Cobertura pour Java (et je suis sûr qu'il existe d'autres outils pour d'autres langages) calcule le nombre de if, etc. dans une fonction pour chaque fonction et le additionne pour la "complexité cyclomatique moyenne ".

Si une fonction/méthode n'a que trois branches, elle obtiendra trois sur cette métrique, ce qui est très bien.

Parfois, il est difficile de suivre cette directive, notamment pour valider les entrées des utilisateurs. Néanmoins, le fait de placer les branches dans différentes méthodes facilite non seulement le développement et la maintenance, mais aussi les tests, car les entrées des méthodes qui effectuent la ramification peuvent être facilement analysées pour voir quelles entrées doivent être ajoutées aux cas de test afin de couvrir les branches qui n'étaient pas couverts.

Si toutes les branches se trouvaient dans une seule méthode, les entrées devraient être suivies depuis le début de la méthode, ce qui entrave la testabilité.

0
MetroidFan2002

Je suppose que vous y trouverez beaucoup de réponses.

Je le décomposerais probablement en fonction des tâches logiques qui étaient effectuées au sein de la fonction. S'il vous semble que votre nouvelle se transforme en roman, je vous suggère de trouver et d'extraire des étapes distinctes.

Par exemple, si vous avez une fonction qui gère une sorte d'entrée de chaîne et renvoie un résultat de chaîne, vous pouvez diviser la fonction en fonction de la logique pour diviser votre chaîne en parties, la logique pour ajouter des caractères supplémentaires et la logique pour la mettre tous à nouveau ensemble comme un résultat formaté.

En bref, tout ce qui rend votre code propre et facile à lire (que ce soit simplement en vous assurant que votre fonction a de bons commentaires ou en le décomposant) est la meilleure approche.

0
Phil.Wheeler

en supposant que vous faites ne chose, la longueur dépendra de:

  • que fais tu
  • quelle langue vous utilisez
  • combien de niveaux d'abstraction vous devez gérer dans le code

60 lignes peuvent être trop longues ou être parfaites. je pense que cela peut être trop long.

0
Ray Tayek

Une chose (et cette chose devrait être évidente d'après le nom de la fonction), mais pas plus qu'un écran de code, malgré tout. Et n'hésitez pas à augmenter la taille de votre police. Et en cas de doute, refactorisez-le en deux fonctions ou plus.

0
gkrogers

Mon idée est que si je dois me demander si c'est trop long, c'est probablement trop long. Cela aide à rendre les fonctions plus petites, dans ce domaine, car cela pourrait aider plus tard dans le cycle de vie de l'application.

0
sokket

Prolongeant l'esprit d'un Tweet d'oncle Bob il y a quelque temps, vous savez qu'une fonction devient trop longue lorsque vous ressentez le besoin de mettre une ligne vierge entre deux lignes de code. L'idée étant que si vous avez besoin d'une ligne vierge pour séparer le code, sa responsabilité et sa portée se séparent à ce stade.

0
Mark Bostleman