web-dev-qa-db-fra.com

Code propre: fonctions avec peu de paramètres

J'ai lu les premiers chapitres de Clean Code de Robert C. Martin, et il me semble que c'est assez bon, mais j'ai un doute, dans une partie il est mentionné que c'est bon (cognitivement ) que les fonctions devraient avoir le moins de paramètres possible, cela suggère même que 3 paramètres ou plus, c'est trop pour une fonction (que je trouve très exagérée et idéaliste), alors j'ai commencé à me demander ...

Les pratiques d'utilisation de variables globales et la transmission de nombreux arguments sur les fonctions seraient de mauvaises pratiques de programmation, mais l'utilisation de variables globales peut réduire considérablement le nombre de paramètres dans les fonctions ...

Je voulais donc savoir ce que vous en pensez, cela vaut-il la peine d'utiliser des variables globales pour réduire le nombre de paramètres des fonctions ou non? Dans quels cas serait-ce?

Je pense que cela dépend de plusieurs facteurs:

  • Taille du code source.
  • Nombre de paramètres en moyenne des fonctions.
  • Nombre de fonctions.
  • Fréquence à laquelle les mêmes variables sont utilisées.

À mon avis, si la taille du code source est relativement petite (comme moins de 600 lignes de code), il existe de nombreuses fonctions, les mêmes variables sont transmises en tant que paramètres et les fonctions ont de nombreux paramètres, alors l'utilisation de variables globales vaudrait la peine, mais je aimerait savoir...

  • Partagez-vous mon opinion?
  • Que pensez-vous des autres cas où le code source est plus gros, etc.?

P.S . J'ai vu ce post , les titres sont très similaires, mais il ne demande pas ce que je veux savoir.

49
OiciTrap

Je ne partage pas ton opinion. À mon avis, l'utilisation de variables globales est une pratique pire que plus de paramètres, quelles que soient les qualités que vous avez décrites. Mon raisonnement est que plus de paramètres peuvent rendre une méthode plus difficile à comprendre, mais les variables globales peuvent causer de nombreux problèmes pour le code, notamment une mauvaise testabilité, des bogues de concurrence et un couplage étroit. Peu importe le nombre de paramètres d'une fonction, elle n'aura pas intrinsèquement les mêmes problèmes que les variables globales.

... les mêmes variables sont passées en paramètres

Ce peut être une odeur de conception. Si vous passez les mêmes paramètres à la plupart des fonctions de votre système, il peut y avoir un problème transversal qui devrait être traité en introduisant un nouveau composant. Je ne pense pas que passer la même variable à de nombreuses fonctions soit un bon raisonnement pour introduire des variables globales.

Dans une modification de votre question, vous avez indiqué que l'introduction de variables globales pourrait améliorer la lisibilité du code. Je ne suis pas d'accord. L'utilisation de variables globales est masquée dans le code d'implémentation tandis que les paramètres de fonction sont déclarés dans la signature. Les fonctions devraient idéalement être pures. Ils ne devraient opérer que sur leurs paramètres et ne devraient pas avoir d'effets secondaires. Si vous avez une fonction pure, vous pouvez raisonner sur la fonction en regardant une seule fonction. Si votre fonction n'est pas pure, vous devez considérer l'état des autres composants, et il devient beaucoup plus difficile de raisonner.

114
Samuel

Vous devez éviter les variables globales comme la peste .

Je ne mettrais pas de limite stricte au nombre d'arguments (comme 3 ou 4), mais vous voulez les garder au minimum, si possible .

Utilisez structs (ou des objets en C++) pour regrouper les variables en une seule entité et les transmettre (par référence) aux fonctions. Habituellement, une fonction reçoit une structure ou un objet (avec quelques éléments différents) qui lui est transmis avec quelques autres paramètres qui indiquent à la fonction de faire quelque chose au struct.

Pour un code modulaire, propre et de bonne odeur, essayez de vous en tenir au principe de responsabilité unique . Faites cela avec vos structures (ou objets), les fonctions et les fichiers source. Si vous faites cela, le nombre naturel de paramètres passés à une fonction sera évident.

68

Nous parlons de charge cognitive, pas de syntaxe. La question est donc ... Qu'est-ce est un paramètre dans ce contexte?

Un paramètre est une valeur qui affecte le comportement de la fonction. Plus il y a de paramètres, plus vous obtenez de combinaisons de valeurs possibles, plus le raisonnement sur la fonction est difficile.

En ce sens, les variables globales que la fonction utilise sont les paramètres. Ce sont des paramètres qui n'apparaissent pas dans sa signature, qui ont des problèmes d'ordre de construction, de contrôle d'accès et de rémanence.

À moins que ce paramètre ne soit ce qu'on appelle un préoccupation transversale, c'est-à-dire un état à l'échelle du programme que tout utilise mais que rien ne modifie (par exemple un objet de journalisation), vous ne devez pas remplacer les paramètres de fonction par des variables globales. Ce seraient toujours des paramètres, mais plus méchants.

55
Quentin

À mon humble avis, votre question est basée sur un malentendu. Dans "Clean Code", Bob Martin ne suggère pas de remplacer les paramètres de fonction répétés par des globaux, ce serait un conseil vraiment horrible. Il suggère de les remplacer par variables de membres privés de la classe de la fonction. Et il propose également petites classes cohérentes (généralement plus petites que les 600 lignes de code que vous avez mentionnées), donc ces variables membres ne sont certainement pas des globales.

Donc, quand vous avez l'opinion dans un contexte avec moins de 600 lignes "utiliser des variables globales en vaudrait la peine" , alors vous partagez parfaitement l'opinion d'Oncle Bob. Bien sûr, il est discutable si "3 paramètres au maximum" est le nombre idéal, et si cette règle conduit parfois à trop de variables membres même dans les petites classes. À mon humble avis, c'est un compromis, il n'y a pas de règle stricte pour tracer la ligne.

34
Doc Brown

Avoir de nombreux paramètres est considéré comme indésirable, mais les transformer en champs ou en variables globales est bien pire car cela ne résout pas le problème réel mais introduit de nouveaux problèmes.

Avoir de nombreux paramètres n'est pas en soi le problème, mais c'est un symptôme que vous pourriez avoir un problème. Considérez cette méthode:

Graphics.PaintRectangle(left, top, length, height, red, green, blue, transparency);

Avoir 7 paramètres est un signe d'avertissement certain. Le problème sous-jacent est que ces paramètres ne sont pas indépendants mais appartiennent à des groupes. left et top appartiennent ensemble comme structure Position-, length et height comme structure Size, et red, blue et green en tant que structure Color. Et peut-être que Color et la transparence appartiennent à une structure Brush? Peut-être que Position et Size appartiennent ensemble dans une structure Rectangle, auquel cas nous pouvons même envisager de la transformer en une méthode Paint sur la Rectangle objet à la place? On peut donc se retrouver avec:

Rectangle.Paint(brush);

Mission accomplie! Mais l'important est que nous avons amélioré la conception globale, et la réduction du nombre de paramètres en est une conséquence . Si nous réduisons simplement le nombre de paramètres sans aborder les problèmes sous-jacents, nous pourrions faire quelque chose comme ceci:

Graphics.left = something;
Graphics.top = something;
Graphics.length = something;
...etc
Graphics.PaintRectangle();

Ici, nous avons obtenu la même réduction du nombre de paramètres, mais nous avons en fait aggravé la conception .

Bottom line: Pour tout conseil de programmation et règles empiriques, il est vraiment important de comprendre le raisonnement sous-jacent.

34
JacquesB

vaut-il la peine d'utiliser des variables globales pour réduire ou non le nombre de paramètres des fonctions?

Ne pas

J'ai lu les premiers chapitres de ce livre

Avez-vous lu le reste du livre?

Un global n'est qu'un paramètre caché. Ils provoquent une douleur différente. Mais ça fait quand même mal. Arrêtez de penser aux moyens de contourner cette règle. Réfléchissez à la façon de le suivre.

Qu'est-ce qu'un paramètre?

C'est des trucs. Dans une boîte joliment étiquetée. Pourquoi est-ce important le nombre de boîtes que vous avez quand vous pouvez y mettre tout ce que vous voulez?

C'est le coût d'expédition et de manutention.

Move(1, 2, 3, 4)

Dites-moi que vous pouvez lire ça. Allez-y, essayez.

Move(PointA, PointB)

Voilà pourquoi.

Cette astuce est appelée introduire l'objet paramètre .

Et oui, c'est juste une astuce si tout ce que vous faites est de compter les paramètres. Ce que vous devriez compter, c'est des IDÉES! Abstractions! Combien me fais-tu penser à la fois? Rester simple.

Maintenant, c'est le même nombre:

Move(xyx, y)

OW! C'est horrible! Qu'est-ce qui a mal tourné ici?

Il ne suffit pas de limiter le nombre d'idées. Ce doivent être des idées claires. Qu'est-ce que c'est qu'un xyx?

Maintenant, c'est encore faible. Quelle est la façon la plus puissante d'y penser?

Les fonctions doivent être petites. Pas plus petit que ça.

Oncle bob

Move(PointB)

Pourquoi faire en sorte que la fonction en fasse plus qu'elle n'en a vraiment besoin? Le principe de responsabilité unique n'est pas seulement pour les classes. Sérieusement, cela vaut la peine de changer une architecture entière juste pour empêcher une fonction de se transformer en cauchemar surchargé avec 10 paramètres parfois liés dont certains ne peuvent pas être utilisés avec d'autres.

J'ai vu du code où le nombre de lignes le plus courant dans une fonction était 1. Sérieusement. Je ne dis pas que vous devez écrire de cette façon, mais sheesh, ne me dites pas qu'un global est la SEULE façon dont vous pouvez vous conformer à cette règle. Arrêtez d'essayer de sortir de la refactorisation de cette fonction correctement. Tu sais que tu peux. Il pourrait se décomposer en quelques fonctions. Il pourrait en fait se transformer en quelques objets. Enfer, vous pourriez même en décomposer une partie en une application entièrement différente.

Le livre ne vous dit pas de compter vos paramètres. Cela vous dit de faire attention à la douleur que vous causez. Tout ce qui résout la douleur résout le problème. Sachez simplement que vous échangez simplement une douleur contre une autre.

7
candied_orange

Je n'utiliserais jamais de variables globales pour réduire les paramètres. La raison en est que les variables globales peuvent être modifiées par n'importe quelle fonction/commande, rendant ainsi l'entrée de fonction peu fiable et sujette à des valeurs qui sont hors de portée de ce que la fonction peut gérer. Que se passe-t-il si la variable a été modifiée pendant l'exécution de la fonction et que la moitié de la fonction avait des valeurs différentes de l'autre moitié?

La transmission de paramètres, d'autre part, restreint la portée de la variable à sa propre fonction de telle sorte que seule la fonction peut modifier un paramètre une fois appelé.

Si vous devez passer des variables globales au lieu d'un paramètre, il est préférable de reconcevoir le code.

Juste mes deux cents.

4
Dimos

Je suis avec l'oncle Bob à ce sujet et je suis d'accord pour dire que plus de 3 paramètres sont à éviter (j'utilise très rarement plus de 3 paramètres dans une fonction). Avoir beaucoup de paramètres sur une seule fonction crée un problème de maintenance plus important et est probablement une odeur que votre fonction fait trop/a trop de responsabilités.

Si vous utilisez plus de 3 dans une méthode dans un langage OO alors vous devriez considérer que les paramètres ne sont pas liés les uns aux autres d'une certaine manière et donc vous devriez vraiment passer un objet à la place?

De plus, si vous créez plus de fonctions (plus petites), vous remarquerez également que les fonctions ont généralement plus de 3 paramètres ou moins. La fonction/méthode d'extraction est votre amie :-).

N'utilisez pas de variables globales pour éviter d'avoir plus de paramètres! C'est échanger une mauvaise pratique contre une pire encore!

2
bytedev

En prenant un exemple de PHP 4, regardez la signature de fonction pour mktime():

  • int mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )

Ne trouvez-vous pas cela déroutant? La fonction est appelée "make time" mais elle prend les paramètres jour, mois et année ainsi que trois paramètres de temps. Est-il facile de se rappeler dans quel ordre ils entrent? Et si vous voyez mktime(1, 2, 3, 4, 5, 2246);? Pouvez-vous comprendre cela sans avoir à vous référer à autre chose? 2246 Est-il interprété comme un temps de 24 heures "22:46"? Que signifient les autres paramètres? Ce serait mieux comme objet.

Passage à PHP 5, il y a maintenant un objet DateTime. Parmi ses méthodes, deux sont appelées setDate() et setTime(). Leurs signatures sont les suivantes:

  • public DateTime setDate ( int $year , int $month , int $day )
  • public DateTime setTime ( int $hour , int $minute [, int $second = 0 ] )

Vous devez toujours vous rappeler que l'ordre des paramètres va du plus grand au plus petit, mais c'est une grande amélioration. Notez qu'il n'y a pas une seule méthode qui vous permet de définir les six paramètres à la fois. Pour ce faire, vous devez effectuer deux appels distincts.

L'oncle Bob évite d'avoir une structure plate. Les paramètres associés doivent être regroupés dans un objet, et si vous avez plus de trois paramètres, il est très probable que vous ayez un pseudo-objet là-bas, ce qui vous donne la possibilité de créer un objet approprié pour une plus grande séparation. Bien que PHP n'a pas de classe Date et Time distincte, vous pouvez considérer que DateTime contient vraiment un Date objet et Time objet.

Vous pourriez éventuellement avoir la structure suivante:

<?php
$my_date = new Date;
$my_date->setDay(5);
$my_date->setMonth(4);
$my_date->setYear(2246);

$my_time = new Time;
$my_time->setHour(1);
$my_time->setMinute(2);
$my_time->setSecond(3);

$my_date_time = new DateTime;
$my_date_time->setTime($my_time);
$my_date_time->setDate($my_date);
?>

Est-il obligatoire de définir deux ou trois paramètres à chaque fois? Si vous souhaitez changer uniquement l'heure ou le jour, il est désormais facile de le faire. Oui, l'objet doit être validé pour s'assurer que chaque paramètre fonctionne avec les autres, mais c'était le cas avant de toute façon.

La chose la plus importante est: est-elle plus facile à comprendre et donc à entretenir? Le bloc de code en bas est plus grand qu'une seule fonction mktime(), mais je dirais que c'est beaucoup plus facile à comprendre; même un non-programmeur n'aurait pas beaucoup de mal à comprendre ce qu'il fait. L'objectif n'est pas toujours un code plus court ou un code plus intelligent, mais un code plus maintenable.

Oh, et n'utilisez pas de globaux!

1
CJ Dennis

Beaucoup de bonnes réponses ici, mais la plupart n'abordent pas le point. Pourquoi ces règles d'or? Il s'agit de portée, de dépendances et de bonne modélisation.

Globalement, les arguments sont pires, car il semble que vous ayez simplifié les choses, mais en fait, vous n'avez caché que la complexité. Vous ne le voyez plus dans le prototype, mais vous devez toujours en être conscient (ce qui est difficile car il n'est plus là pour vous de le voir) et vous familiariser avec la logique de la fonction ne vous aidera pas car il peut être une autre logique cachée qui interfère derrière votre dos. Votre portée est allée partout et vous avez introduit une dépendance sur quoi que ce soit, car tout ce qui peut maintenant jouer avec votre variable. Pas bon.

La principale chose à maintenir pour une fonction est que vous pouvez comprendre ce qu'elle fait en regardant le prototype et en appelant. Le nom doit donc être clair et sans ambiguïté. Mais aussi, plus il y a d'arguments, plus il sera difficile de comprendre ce qu'il fait. Cela élargit la portée dans votre tête, trop de choses se passent, c'est pourquoi vous voulez limiter le nombre. Peu importe le type d'arguments avec lesquels vous traitez, certains sont pires que d'autres. Un booléen optionnel supplémentaire qui permet un traitement insensible à la casse ne rend pas la fonction plus difficile à comprendre, vous ne voudriez donc pas en faire trop. En remarque, les énumérations font de meilleurs arguments que les booléens car la signification d'une énumération est évidente dans l'appel.

Le problème typique n'est pas que vous écriviez une nouvelle fonction avec une énorme quantité d'arguments, vous commencerez avec seulement quelques-uns lorsque vous ne réaliserez pas encore à quel point le problème que vous résolvez est vraiment complexe. À mesure que votre programme évolue, les listes d'arguments ont tendance à s'allonger progressivement. Une fois qu'un modèle est dans votre esprit, vous voulez le garder car c'est une référence sûre que vous connaissez. Mais rétrospectivement, le modèle n'est peut-être pas si bon. Vous avez manqué une étape ou trop dans la logique et vous n'avez pas reconnu une ou deux entités. "OK ... je pourrais recommencer et passer un jour ou deux à refactoriser mon code ou ... je pourrais ajouter cet argument pour que je puisse le faire faire la bonne chose après tout et en finir avec ça. Pour l'instant. Pour ce scénario . Pour éliminer ce bug de mon assiette afin que je puisse déplacer le pense-bête à terminé. "

Plus vous allez souvent avec la deuxième solution, plus la maintenance sera coûteuse et plus le refactoriseur sera difficile et peu attrayant.

Il n'y a pas de solution de plaque de chaudière pour réduire le nombre d'arguments dans une fonction existante. Le simple fait de les regrouper en composés ne facilite pas vraiment les choses, c'est juste une autre façon d'essuyer la complexité sous le tapis. Pour bien faire les choses, il faut revoir la pile des appels et reconnaître ce qui manque ou a été mal fait.

1
Martin Maat

L'utilisation de variables globales semble toujours un moyen facile de coder (en particulier dans un petit programme), mais cela rendra votre code difficile à étendre.

Oui, vous pouvez réduire le nombre de paramètres dans une fonction en utilisant un tableau pour lier les paramètres dans une entité.

function <functionname>(var1,var2,var3,var4.....var(n)){}

La fonction ci-dessus sera modifiée et changée en [en utilisant un tableau associatif] -

data=array(var1->var1,
           var2->var2
           var3->var3..
           .....
           ); // data is an associative array

function <functionname>(data)

Je suis d'accord avec réponse de robert bristow-johnson : vous pouvez même utiliser une structure pour lier des données dans une seule entité.

1
Narender Parmar

Une alternative valide à de nombreux paramètres de fonction consiste à introduire un objet de paramètre. Ceci est utile si vous avez une méthode composée qui transmet (presque) tous ses paramètres à un tas d'autres méthodes.

Dans les cas simples, il s'agit d'un simple DTO n'ayant que les anciens paramètres comme propriétés.

1
Timothy Truckle

Plusieurs fois, j'ai constaté que le regroupement de nombreux paramètres envoyés ensemble améliorait les choses.

  • Il vous oblige à donner un bon nom à cette agrégation de concepts qui sont utilisés ensemble. Si vous utilisez ces paramètres ensemble, il se peut qu'ils aient une relation (ou que vous ne devriez pas du tout les utiliser ensemble).

  • Une fois avec l'objet, je trouve généralement qu'une certaine fonctionnalité peut être déplacée par rapport à cet objet apparemment stuypide. Il est généralement facile à tester et avec une grande cohésion et un faible couplage, ce qui est encore mieux.

  • La prochaine fois que j'aurai besoin d'ajouter un nouveau paramètre, j'ai un bel endroit pour l'inclure sans changer la signature de beaucoup de méthodes.

Il est donc possible que cela ne fonctionne pas à 100%, mais demandez-vous si cette liste de paramètres doit être regroupée dans un objet. Et s'il vous plaît. N'utilisez pas de classes non typées comme Tuple pour éviter de créer l'objet. Vous utilisez une programmation orientée objet, cela ne vous dérange pas de créer plus d'objets si vous en avez besoin.

0
Borjab

Avec tout le respect que je vous dois, je suis sûr que vous avez complètement raté le point d'avoir un petit nombre de paramètres dans une fonction.

L'idée est que le cerveau ne peut contenir autant d'informations "actives" à la fois, et si vous avez n paramètres dans une fonction, vous avez n plus de morceaux de " informations actives "qui doivent être dans votre cerveau pour comprendre facilement et avec précision ce que fait le morceau de code (Steve McConnell, dans Code Complete (un bien meilleur livre, IMO), dit quelque chose de similaire à propos de 7 variables dans le corps d'une méthode: rarement atteignons-nous cela, mais plus et vous perdez la capacité de garder tout droit dans votre tête).

L'intérêt d'un petit nombre de variables est de pouvoir maintenir les exigences cognitives de travailler avec ce code, afin que vous puissiez y travailler (ou le lire) plus efficacement. Une cause secondaire de cela est que le code bien factorisé aura tendance à avoir de moins en moins de paramètres (par exemple, le code mal factorisé a tendance à regrouper un tas de choses dans un gâchis).

En passant des objets au lieu de valeurs, vous gagnez peut-être un niveau d'abstraction pour votre cerveau car maintenant il doit réaliser oui, j'ai un SearchContext avec lequel travailler ici au lieu de penser aux 15 propriétés qui pourrait être dans ce contexte de recherche.

En essayant d'utiliser des variables globales, vous êtes complètement allé dans la mauvaise direction. Maintenant, non seulement vous n'avez pas résolu les problèmes d'avoir trop de paramètres pour une fonction, vous avez pris ce problème et l'avez jeté n problème bien meilleur que vous devez maintenant porter dans votre esprit!

Maintenant, au lieu de travailler uniquement au niveau de la fonction, vous devez également prendre en compte la portée globale de votre projet (stupide, c'est terrible! Je frissonne ...). Vous n'avez même pas toutes vos informations devant vous dans la fonction (merde, quel est ce nom de variable globale?) (J'espère que cela n'a pas été changé par quelque chose d'autre depuis que j'ai appelé cette fonction).

Les variables de portée mondiale sont l'une des pires choses à voir dans un projet (grand ou petit). Ils dénotent un handicap pour une bonne gestion de la portée, ce qui est une compétence très fondamentale pour la programmation. Ils. Sont. Mal.

En déplaçant des paramètres hors de votre fonction et en les mettant dans des globaux, vous vous êtes tiré dans le plancher (ou la jambe, ou le visage). Et Dieu ne vous défende jamais de décider que hé, je peux réutiliser ce global pour autre chose ... mon esprit tremble à cette pensée.

L'idéal est de garder les choses faciles à gérer, et les variables globales ne feront PAS cela. J'irais jusqu'à dire que c'est l'une des pires choses que vous puissiez faire pour aller dans la direction opposée.

0
jleach