Y a-t-il une raison pour ne pas modifier les valeurs des paramètres passés par valeur?

9

Existe-t-il des arguments objectifs et supportables en ingénierie logicielle pour ou contre la modification des valeurs des paramètres par valeur dans le corps d'une fonction?

Un problème récurrent (surtout pour le plaisir) sur mon équipe est de savoir si les paramètres passés par valeur doivent être modifiés. Quelques membres de l'équipe sont convaincus que les paramètres ne doivent jamais être attribués, de sorte que la valeur initialement transmise à la fonction peut toujours être interrogée. Je ne suis pas d'accord et maintiens que les paramètres ne sont rien de plus que des variables locales initialisées par la syntaxe d'appel de la méthode; si la valeur d'origine d'un paramètre par valeur est importante, une variable locale peut être déclarée pour stocker explicitement cette valeur. Je ne suis pas sûr que chacun de nous soutienne très bien notre position.

S'agit-il d'un conflit religieux non résolu ou y a-t-il de bonnes raisons objectives d'ingénierie logicielle dans les deux sens?

Remarque: La question de principe demeure indépendamment des détails de mise en œuvre de la langue particulière. En JavaScript, par exemple, où la liste des arguments est toujours dynamique, les paramètres peuvent être considérés comme du sucre syntaxique pour l'initialisation de variable locale à partir de l' argumentsobjet. Même ainsi, on pourrait traiter les identificateurs déclarés par paramètres comme "spéciaux" car ils captent toujours le passage d'informations de l'appelant à l'appelé.

Joshua Honig
la source
Il est toujours spécifique à la langue; soutenant qu'il est indépendant de la langue parce que "il en est ainsi, du point de vue de ma (langue préférée)" est une forme d'argument invalide. Une deuxième raison pour laquelle il est spécifique à une langue est qu'une question qui n'a pas de réponse universelle pour toutes les langues est soumise aux cultures et aux normes de chaque langue; dans ce cas, différentes normes spécifiques à la langue donnent des réponses différentes à cette question.
rwong
Le principe que les membres de votre équipe essaient de vous enseigner est «une variable, un objectif». Malheureusement, vous n'allez pas trouver de preuve définitive de toute façon. Pour ce que ça vaut, je ne me souviens pas d'avoir coopté une variable de paramètre à d'autres fins, ni de l'avoir envisagée. Je n'ai jamais pensé que ce serait une bonne idée, et je ne pense toujours pas que ce soit le cas.
Robert Harvey
Je pensais que cela devait avoir été demandé auparavant, mais la seule dupe que j'ai pu trouver est cette ancienne question SO .
Doc Brown
3
Il est considéré comme moins sujet aux erreurs pour un langage d'interdire la mutation d'argument, tout comme les concepteurs de langage fonctionnel considèrent que les affectations «laisser» sont moins sujettes aux erreurs. Je doute que quiconque ait prouvé cette hypothèse à l'aide d'une étude.
Frank Hileman

Réponses:

15

Je ne suis pas d'accord et maintiens que les paramètres ne sont rien de plus que des variables locales initialisées par la syntaxe d'appel de la méthode

J'adopte une troisième position: les paramètres sont comme des variables locales: les deux doivent être traités comme immuables par défaut. Les variables sont donc attribuées une seule fois puis lues uniquement, et non modifiées. Dans le cas des boucles, for each (x in ...)la variable est immuable dans le contexte de chaque itération. Les raisons en sont:

  1. cela rend le code plus facile à "exécuter dans ma tête",
  2. il permet des noms plus descriptifs pour les variables.

Face à une méthode avec un tas de variables qui sont affectées puis ne changent pas, je peux me concentrer sur la lecture du code, plutôt que d'essayer de me souvenir de la valeur actuelle de chacune de ces variables.

Face à cette même méthode, cette fois avec moins de variables, mais qui changent tout le temps de valeur, je dois maintenant me souvenir de l'état actuel de ces variables, ainsi que travailler sur ce que fait le code.

D'après mon expérience, le premier est beaucoup plus facile pour mon pauvre cerveau. D'autres gens plus intelligents que moi n'ont peut-être pas ce problème, mais cela m'aide.

Quant à l'autre point:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

contre

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

Exemples artificiels, mais à mon avis, il est beaucoup plus clair de savoir ce qui se passe dans le deuxième exemple en raison des noms de variables: ils transmettent beaucoup plus d'informations au lecteur que cette masse de données rdans le premier exemple.

David Arno
la source
"les variables locales .... devraient être traitées comme immuables par défaut." - Quoi?
whatsisname
1
Il est assez difficile d'exécuter des forboucles avec des variables immuables. Encapsuler la variable dans la fonction ne vous suffit pas? Si vous rencontrez des difficultés à suivre vos variables dans une simple fonction, vos fonctions sont peut-être trop longues.
Robert Harvey
@RobertHarvey for (const auto thing : things)est une boucle for, et la variable qu'elle introduit est (localement) immuable. for i, thing in enumerate(things):ne mute pas non plus les habitants
Caleth
3
C'est à mon humble avis en général un très bon modèle mental. Cependant, je voudrais ajouter une chose: il est souvent acceptable d'initialiser une variable en plusieurs étapes et de la traiter comme immuable par la suite. Ce n'est pas contre la stratégie générale présentée ici, mais contre le fait de la suivre toujours littéralement.
Doc Brown
3
Wow, en lisant ces commentaires, je peux voir que beaucoup de gens ici n'ont évidemment jamais étudié le paradigme fonctionnel.
Joshua Jones
4

S'agit-il d'un conflit religieux non résolu ou y a-t-il de bonnes raisons objectives d'ingénierie logicielle dans les deux sens?

C'est une chose que j'aime vraiment en C ++ (bien écrit): vous pouvez voir à quoi vous attendre. const string& x1est une référence constante, string x2est une copie et const string x3est une copie constante. Je sais ce qui se passera dans la fonction. x2 sera modifié, car s'il ne l'était pas, il n'y aurait aucune raison de le rendre non constant.

Donc, si vous avez un langage qui le permet , déclarez ce que vous allez faire avec les paramètres. Cela devrait être le meilleur choix pour toutes les personnes impliquées.

Si votre langue ne le permet pas, je crains qu'il n'y ait pas de solution miracle. Les deux sont possibles. La seule ligne directrice est le principe de la moindre surprise. Adoptez une approche et suivez-la dans toute votre base de code, ne la mélangez pas.

nvoigt
la source
Une autre bonne chose est que int f(const T x)et int f(T x)ne sont différents que dans le corps fonctionnel.
Déduplicateur
3
Une fonction C ++ avec un paramètre comme const int xjuste pour clarifier x n'est pas modifiée à l'intérieur? Cela ressemble à un style très étrange pour moi.
Doc Brown
@DocBrown Je ne juge aucun des deux styles, je dis simplement que quelqu'un qui pense que les paramètres ne devraient pas être modifiés peut faire en sorte que le compilateur le garantisse et que quelqu'un qui pense que les modifier est bien peut le faire aussi. Les deux intentions peuvent être clairement communiquées à travers la langue elle-même et c'est un grand avantage par rapport à une langue qui ne peut pas la garantir de toute façon et où vous devez vous fier à des directives arbitraires et espérer que tout le monde les lit et s'y conforme.
nvoigt
@nvoigt: si quelqu'un préfère modifier un paramètre de fonction ("par valeur"), il constsupprimerait simplement la liste des paramètres si cela le gêne. Je ne pense donc pas que cela réponde à la question.
Doc Brown
@DocBrown Alors ce n'est plus const. Ce n'est pas important que ce soit const ou non, l'important est que le langage inclut un moyen de communiquer cela au développeur sans aucun doute. Ma réponse est que cela n'a pas d'importance, tant que vous le communiquez clairement , ce que le C ++ facilite, d'autres non et vous avez besoin de conventions.
nvoigt
1

Oui, c'est un "conflit religieux", sauf que Dieu ne lit pas les programmes (pour autant que je sache), donc il est déclassé en un conflit de lisibilité qui devient une question de jugement personnel. Et je l'ai certainement fait et vu fait dans les deux sens. Par exemple,

propagation nulle (objet OBJECT *, double t0, double dt) {
  double t = t0; / * copie locale de t0 pour éviter de changer sa valeur * /
  tout (peu importe) {
    timestep_the_object (...);
    t + = dt; }
  revenir; }

Donc ici, juste à cause du nom de l'argument t0 , je choisirais probablement de ne pas changer sa valeur. Mais supposons plutôt que nous ayons simplement changé le nom de cet argument en tNow , ou quelque chose comme ça. Ensuite, j'avais oublié plus que probablement une copie locale et j'écrivais simplement tNow + = dt; pour cette dernière déclaration.

Mais au contraire, supposons que je savais que le client pour lequel le programme a été écrit est sur le point d'embaucher de nouveaux programmeurs avec très peu d'expérience, qui liraient et travailleraient sur ce code. Ensuite, je craindrais probablement de les confondre avec la valeur par rapport à la référence, tandis que leurs esprits sont 100% occupés à essayer de digérer toute la logique métier. Donc, dans ce genre de cas, je déclare toujours une copie locale de tout argument qui sera modifié, qu'il soit plus ou moins lisible pour moi.

Les réponses à ces types de questions de style émergent avec l'expérience et ont rarement une réponse religieuse canonique. Quiconque pense qu'il y a une et une seule réponse (et qu'il le sait :) a probablement besoin de plus d'expérience.

John Forkosh
la source