Techniques pour minimiser le nombre d'arguments de fonction

13

Dans Clean Code, il est écrit que "le nombre idéal d'arguments pour une fonction est zéro". Les raisons sont expliquées et ont un sens. Ce que je recherche, ce sont des techniques pour refactoriser les méthodes avec 4 arguments ou plus pour résoudre ce problème.

Une façon est d'extraire les arguments dans une nouvelle classe, mais cela conduirait sûrement à une explosion de classes? Et ces classes sont susceptibles de se retrouver avec des noms qui violent certaines des règles de dénomination (se terminant par "Data" ou "Info", etc.)?

Une autre technique consiste à faire des variables utilisées par plusieurs fonctions une variable membre privée pour éviter de les transmettre, mais cela étend la portée de la variable, éventuellement de telle sorte qu'elle soit ouverte aux fonctions qui n'en ont pas réellement besoin.

Je cherche juste des moyens de minimiser les arguments de fonction, après avoir accepté les raisons pour lesquelles c'est une bonne idée de le faire.

Neil Barnwell
la source
21
Tbh je ne suis pas du tout d'accord avec le code propre. Si le nombre d'arguments d'une fonction est nul, cela implique que la fonction a des effets secondaires et change probablement d'état quelque part. Bien que je convienne que moins de 4 arguments peuvent être une bonne règle de base - je préfère avoir une fonction avec 8 arguments qui est statique et n'a pas d'effets secondaires qu'une fonction non statique avec zéro arguments qui change d'état et a des effets secondaires .
wasatz
4
" Dans Clean Code, il est écrit que" le nombre idéal d'arguments pour une fonction est zéro ". " Vraiment? C'est tellement faux! Le nombre idéal de paramètres est un, avec une valeur de retour dérivée de façon déterministe de ce paramètre. En pratique, le nombre de paramètres importe peu cependant; ce qui importe, c'est que, dans la mesure du possible, la fonction soit pure (c'est-à-dire qu'elle ne tire sa valeur de retour que de ses paramètres sans effets secondaires).
David Arno
2
Eh bien, le livre souligne plus tard que les effets secondaires ne sont pas souhaitables ...
Neil Barnwell
2
Reproduction possible de stratégies pour l'encapsulation des paramètres
gnat

Réponses:

16

La chose la plus importante à retenir est que ce sont des lignes directrices, pas des règles.

Il y a des cas où une méthode doit simplement prendre un argument. Pensez à la +méthode des nombres, par exemple. Ou la addméthode pour une collection.

En fait, on pourrait même dire que ce que cela signifie d'ajouter deux nombres dépend du contexte, par exemple dans ℤ 3 + 3 == 6, mais dans ℤ | 5 3 + 3 == 2 , donc vraiment l'opérateur d'addition devrait être une méthode sur un objet de contexte qui prend deux arguments au lieu d'un méthode sur les nombres qui prend un argument.

De même, une méthode pour comparer deux objets doit être soit une méthode d'un objet prenant l'autre comme argument, soit une méthode du contexte, prenant deux objets en argument, donc il n'a tout simplement pas de sens d'avoir une méthode de comparaison avec moins d'un argument.

Cela dit, deux choses peuvent être faites pour réduire le nombre d'arguments pour une méthode:

  • Rendre la méthode elle-même plus petite : peut-être que si la méthode a besoin d'autant d'arguments, elle en fait trop?
  • Une abstraction manquante : si les arguments sont étroitement corrélés, peut-être qu'ils vont ensemble et qu'il y a une abstraction qui vous manque? (Exemple de manuel canonique: au lieu de deux coordonnées, passez un Pointobjet, ou au lieu de transmettre un nom d'utilisateur et un e-mail, passez un IdCardobjet.)
  • État de l'objet : si l'argument est requis par plusieurs méthodes, il doit peut-être faire partie de l'état de l'objet. S'il n'est nécessaire que par certaines des méthodes mais pas par d'autres, peut-être que l'objet en fait trop et devrait en fait être deux objets.

Une façon est d'extraire les arguments dans une nouvelle classe, mais cela conduirait sûrement à une explosion de classes?

Si votre modèle de domaine comporte de nombreux types de choses, votre code se retrouvera avec de nombreux types d'objets différents. Il n'y a rien de mal à cela.

Et ces classes sont susceptibles de se retrouver avec des noms qui violent certaines des règles de dénomination (se terminant par "Data" ou "Info", etc.)?

Si vous ne trouvez pas un nom correct, vous avez peut-être regroupé trop d'arguments ou trop peu. Donc, soit vous avez juste un fragment d'une classe, soit vous avez plus d'une classe.

Une autre technique consiste à faire des variables utilisées par plusieurs fonctions une variable membre privée pour éviter de les transmettre, mais cela étend la portée de la variable, éventuellement de telle sorte qu'elle soit ouverte aux fonctions qui n'en ont pas réellement besoin.

Si vous avez un groupe de méthodes qui fonctionnent toutes sur les mêmes arguments, et un autre groupe de méthodes qui ne le font pas, elles appartiennent peut-être à différentes classes.

Notez combien de fois j'ai utilisé le mot «peut-être»? C'est pourquoi ce sont des lignes directrices, pas des règles. Peut-être que votre méthode avec 4 paramètres est parfaitement bien!

Jörg W Mittag
la source
7
@ BrunoSchäpper: Bien sûr: (1) " Rendre la méthode elle-même plus petite: Peut-être que si la méthode a besoin d'autant d'arguments, elle en fait trop? ". Le nombre de paramètres est un mauvais test. Les paramètres optionnels / booléens et de nombreuses lignes de code sont des indicateurs forts d'une méthode qui en fait trop. Beaucoup de params sont au mieux faibles. (2) " État d'objet: si l'argument est nécessaire par plusieurs méthodes, il devrait peut-être faire partie de l'état d'objet ". Non, non et trois fois, non. Minimiser l'état de l'objet; pas de paramètres de fonction. Si possible, passez une valeur à toutes les méthodes via des paramètres pour éviter l'état de l'objet.
David Arno
L'exemple que vous avez donné pour l'ajout est carrément faux. La addfonction pour les nombres naturels et la addfonction pour l'anneau d'entiers mod n sont deux actions de fonctions différentes sur deux types différents. Je ne comprends pas non plus ce que vous entendez par «contexte».
gardenhead
Thx @DavidArno. 1) d'accord, pas un indicateur fort en soi. Mais bon quand même. Je vois souvent quelques méthodes, avec quelques objets passés autour. Il n'y a pas d'état d'objet. C'est bien, mais 2) une meilleure option IMHO refactorise ces méthodes, déplace l'état implicite vers une nouvelle classe, qui prend tous ces paramètres comme arguments explicites. Vous vous retrouvez avec une méthode publique d'argument zéro et de nombreuses méthodes internes d'argument zéro à un. L'État n'est pas public, mondial ou même maintenu en vie longtemps, mais le code est beaucoup plus propre.
Bruno Schäpper
6

Notez que zéro argument n'implique pas d'effets secondaires, car votre objet est un argument implicite. Regardez le nombre de méthodes sans arité de la liste immuable de Scala , par exemple.

Une technique utile que j'appelle la technique de "focalisation de l'objectif". Lorsque vous effectuez la mise au point d'un objectif d'appareil photo, il est plus facile de voir le vrai point de mise au point si vous le prenez trop loin, puis de le reculer au bon point. Il en va de même pour le refactoring logiciel.

Surtout si vous utilisez le contrôle de version distribué, les modifications logicielles sont faciles à expérimenter, voyez si vous aimez leur apparence, et reculez si vous ne le faites pas, mais pour une raison quelconque, les gens semblent souvent réticents à le faire.

Dans le contexte de votre question actuelle, cela signifie écrire les versions zéro ou un argument, avec plusieurs fonctions séparées d'abord, puis il est relativement facile de voir quelles fonctions doivent être combinées pour plus de lisibilité.

Notez que l'auteur est également un ardent défenseur du développement piloté par les tests, qui a tendance à produire des fonctions de faible densité au début, car vous commencez avec vos cas de test triviaux.

Karl Bielefeldt
la source
1
Comme votre analogie de "mise au point de l'objectif" - Surtout lors de la refactorisation, il est important d'utiliser l'objectif grand angle au lieu de l'objectif gros plan. Et regarder # de paramètres est tout simplement trop proche
tofro
0

Une approche qui vise simplement (et naïvement - ou devrais-je même dire à l' aveuglette ) à réduire le nombre d'arguments aux fonctions est probablement fausse. Il n'y a absolument rien de mal avec les fonctions ayant un grand nombre d'arguments. S'ils sont requis par la logique, eh bien ils sont nécessaires ... Une longue liste de paramètres ne m'inquiète pas du tout - tant qu'elle est correctement formatée et commentée pour la lisibilité.

Dans le cas où tous ou un sous-ensemble d'arguments appartiennent à une entité logique unique et sont généralement transmis en groupes dans votre programme, il peut être judicieux de les regrouper dans un conteneur - généralement une structure ou un autre objet. Des exemples typiques peuvent être une sorte de type de données de message ou d' événement .

Vous pouvez facilement exagérer cette approche - une fois que vous constatez que l'emballage et le déballage de trucs vers et depuis de tels conteneurs de transport génèrent plus de surcharge que cela améliore la lisibilité, vous êtes probablement allé trop loin.

OTOH, de grandes listes de paramètres peuvent être un signe que votre programme n'est peut-être pas correctement structuré - peut-être la fonction qui nécessite un si grand nombre de paramètres essaie simplement d'en faire trop et devrait être divisée en plusieurs fonctions plus petites. Je préfère commencer ici que de m'inquiéter du nombre de paramètres.

tofro
la source
5
Évidemment, réduire aveuglément le nombre d'arguments est une erreur. Mais je ne suis pas d'accord avec "Il n'y a absolument rien de mal à ce que les fonctions aient un grand nombre d'arguments." . À mon avis, lorsque vous frappez une fonction avec un grand nombre d'arguments, dans 99,9% de tous les cas, il existe un moyen d'améliorer la structure du code de manière délibérée, ce qui (également) réduit le nombre d'arguments de la fonction.
Doc Brown
@DocBrown C'est pourquoi il y a ce dernier paragraphe et la recommandation de commencer par là .... Et un autre: Vous n'avez probablement jamais essayé de programmer contre une API MS Windows;)
tofro
"Peut-être que la fonction qui nécessite un si grand nombre de paramètres essaie simplement d'en faire trop et devrait être divisée en plusieurs fonctions plus petites." Je suis tout à fait d'accord, bien qu'en pratique ne vous retrouvez-vous pas simplement avec une autre fonction plus haut dans la pile qui appelle ces plusieurs fonctions plus petites? Vous pouvez ensuite les refactoriser en un objet, mais cet objet aura un ctor. Vous pouvez utiliser un constructeur bla bla bla. Le fait est que c'est une régression infinie - quelque part, il y a un certain nombre de valeurs qui sont nécessaires pour que le logiciel fasse son travail, et ils doivent accéder à ces fonctions d'une manière ou d'une autre.
Neil Barnwell
1
@NeilBarnwell Dans le cas idéal (qui mérite d'être refactorisé), vous pourriez être en mesure de diviser une fonction qui a besoin de 10 arguments en trois qui ont besoin de 3-4 arguments chacun. Dans le cas pas si idéal, vous vous retrouvez avec trois fonctions qui ont besoin de 10 arguments chacune (mieux vaut laisser cela, alors). En ce qui concerne votre argument de pile plus élevé: D'accord - Cela pourrait être le cas, mais pas nécessairement - les arguments viennent de quelque part, et cette récupération pourrait également être quelque part à l'intérieur de la pile et doit simplement être placée au bon endroit - Kilométrage a tendance à varier.
tofro
La logique logicielle ne requiert jamais plus de quatre paramètres. Seul un compilateur peut le faire.
theDoctor
0

Il n'y a pas de moyen magique: vous devez maîtriser le domaine problématique pour découvrir la bonne architecture. C'est le seul moyen de refactoriser: maîtriser le domaine problématique. Plus de quatre paramètres est juste un pari sûr que votre architecture actuelle est défectueuse et erronée.

Les seules exceptions sont les compilateurs (métaprogrammes) et les simulations, où la limite est théoriquement 8, mais probablement seulement 5.

le docteur
la source