Les «variables doivent-elles vivre dans la plus petite portée possible» incluent-elles le cas «les variables ne devraient pas exister si possible»?

32

Selon la réponse acceptée sur " Justification de préférer les variables locales aux variables d'instance? ", Les variables devraient vivre dans la plus petite portée possible.
Simplifier le problème dans mon interprétation, cela signifie que nous devrions reformer ce type de code:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

dans quelque chose comme ça:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

mais selon "l'esprit" de "les variables doivent vivre dans la plus petite portée possible", "les variables n'ayant jamais" n'ont-elles pas une portée plus petite que "les variables"? Je pense donc que la version ci-dessus devrait être refactorisée:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

donc cela getResult()n'a pas de variables locales du tout. Est-ce vrai?

ocomfd
la source
72
La création de variables explicites présente l’avantage de devoir les nommer. L'introduction de quelques variables peut rapidement transformer une méthode opaque en une méthode lisible.
Jared Goguen
11
Honnêtement, un exemple en termes de noms de variables a et b est trop artificiel pour démontrer la valeur des variables locales. Heureusement, vous avez quand même de bonnes réponses.
Doc Brown
3
Dans votre deuxième extrait, vos variables ne sont pas vraiment des variables . Ce sont effectivement des constantes locales, car vous ne les modifiez pas. Le compilateur Java les traitera de la même manière si vous les définissez final, car ils sont dans la portée locale et le compilateur sait ce qui va se passer avec eux. C'est une question de style et d'opinion, si vous devez réellement utiliser un finalmot-clé ou non.
Hyde
2
@JaredGoguen La création de variables explicites s'accompagne de la charge de devoir les nommer et de l'avantage de pouvoir les nommer.
Bergi
2
Absolument zéro des règles de style de code comme « variables doivent vivre dans la plus petite portée possible » sont applicables universellement sans pensée. En tant que tel, vous ne voudrez jamais baser une décision de style de code sur le raisonnement du formulaire de cette question, dans lequel vous prenez une simple directive et l'étendez dans une zone moins couverte ("les variables devraient si possible être de petite taille" -> "les variables ne devraient pas exister si possible"). Soit de bonnes raisons justifient votre conclusion, soit votre conclusion est une extension inappropriée; De toute façon, vous n'avez pas besoin de la directive d'origine.
Ben

Réponses:

110

Non, il y a plusieurs raisons pour lesquelles:

  1. Les variables avec des noms significatifs peuvent rendre le code plus facile à comprendre.
  2. Diviser des formules complexes en étapes plus petites peut rendre le code plus facile à lire.
  3. Caching
  4. Tenir des références aux objets afin qu'ils puissent être utilisés plus d'une fois.

Etc.

Robert Harvey
la source
22
Il convient également de mentionner que la valeur va être stockée dans la mémoire quelle qu’elle soit, elle finit donc toujours par avoir la même portée. Peut-être aussi le nommer (pour les raisons que Robert mentionne ci-dessus)!
Maybe_Factor
5
@ Maybe_Factor La directive n'est pas vraiment là pour des raisons de performance; c'est à quel point il est facile de maintenir le code. Mais cela signifie tout de même que les sections locales significatives valent mieux qu'une expression énorme, à moins que vous ne composiez que des fonctions bien nommées et conçues (par exemple, il n'y a aucune raison de le faire var taxIndex = getTaxIndex();).
Luaan
16
Tous sont des facteurs importants. La façon dont je le vois: la brièveté est un objectif; la clarté en est une autre; la robustesse en est une autre; la performance en est une autre; etc. Aucun de ceux-ci ne sont des objectifs absolus , et ils sont parfois en conflit. Notre travail consiste donc à trouver le meilleur moyen d’ équilibrer ces objectifs.
vertiges
8
Le compilateur s’occupera de 3 & 4.
Stop Harming Monica
9
Le numéro 4 peut être important pour la correction. Si la valeur d'un appel de fonction peut changer (par exemple now(),), supprimer la variable et appeler la méthode plusieurs fois peut entraîner des bogues. Cela peut créer une situation très subtile et difficile à déboguer. Cela peut sembler évident, mais si vous êtes dans une mission de refactoring à l'aveugle pour supprimer des variables, il est facile d'introduire des failles.
JimmyJames
16

D'accord, les variables qui ne sont pas nécessaires et n'améliorent pas la lisibilité du code devraient être évitées. Plus il y a de variables qui entrent dans la portée à un moment donné du code, plus ce code est complexe à comprendre.

Je ne vois pas vraiment l'avantage des variables aet bdans votre exemple, j'écrirais donc la version sans variables. D'un autre côté, la fonction est si simple au départ que je ne pense pas que cela compte beaucoup.

Plus la fonction est longue et plus la portée des variables est longue, plus le problème est complexe.

Par exemple si vous avez

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

Au sommet d'une fonction plus importante, vous augmentez la charge mentale de comprendre le reste du code en introduisant trois variables plutôt qu'une. Vous devez lire le reste du code pour voir si aou bsont utilisés à nouveau. Les sections locales dont la portée est plus longue que nécessaire ne nuisent pas à la lisibilité générale.

Bien sûr , dans le cas où une variable est nécessaire (par exemple pour stocker un résultat temporaire) ou si une variable n'améliorer la lisibilité du code, il doit être conservé.

JacquesB
la source
2
Bien sûr, au point où une méthode devient si longue que le nombre de sections locales est trop important pour être facilement maintenable, vous souhaiterez probablement la dissocier de toute façon.
Luaan
4
L'un des avantages des variables "étrangères", comme var result = getResult(...); return result;celle-ci, est que vous pouvez placer un point d'arrêt returnet apprendre ce que resultc'est exactement .
Joker_vD
5
@Joker_vD: Un débogueur digne de ce nom devrait pouvoir vous permettre de faire tout cela de toute façon (voir les résultats d'un appel de fonction sans le stocker dans une variable locale + définir un point d'arrêt à la sortie de la fonction).
Christian Hackl
2
@ChristianHackl Très peu de déboguages ​​vous permettent de le faire sans avoir à installer des montres complexes qui prennent du temps à ajouter (et si les fonctions ont des effets secondaires, tout peut tout casser). Je prendrai la version avec variables pour déboguer n’importe quel jour de la semaine.
Gabe Sechan
1
@ChristianHackl et pour continuer sur cette lancée même si le débogueur ne vous laisse pas le faire par défaut car le débogueur est horriblement conçu, vous pouvez simplement modifier le code local sur votre machine pour stocker le résultat, puis l'extraire dans le débogueur . Il n'y a toujours pas de raison de stocker une valeur dans une variable pour seulement cette fin.
Le grand canard
11

En plus des autres réponses, je voudrais signaler autre chose. L’avantage de limiter la portée d’une variable n’est pas simplement de réduire le nombre de codes ayant accès à la variable dans la syntaxe, mais également de réduire le nombre de chemins de contrôle pouvant éventuellement modifier une variable (en lui affectant une nouvelle valeur ou en appelant une nouvelle méthode). une méthode de mutation sur l'objet existant contenu dans la variable).

Les variables à portée de classe (instance ou statique) ont beaucoup plus de chemins de flux de contrôle possibles que les variables à portée locale, car elles peuvent être mutées par des méthodes pouvant être appelées dans n'importe quel ordre, nombre de fois, et souvent par code en dehors de la classe. .

Jetons un coup d'oeil à votre getResultméthode initiale :

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Maintenant, les noms getAet getBpeuvent suggérer qu’ils assigneront à this.aet this.b, nous ne pouvons pas savoir avec certitude en regardant simplement getResult. Ainsi, il est possible que les valeurs this.aet this.bpassées dans la mixméthode proviennent plutôt de l'état de l' thisobjet avant qu'il ait getResultété appelé, ce qui est impossible à prédire puisque les clients contrôlent comment et quand les méthodes sont appelées.

Dans le code révisé avec les variables locales aet locales b, il est clair qu'il existe exactement un flux de contrôle (sans exception) de l'affectation de chaque variable à son utilisation, car les variables sont déclarées juste avant leur utilisation.

Ainsi, le fait de déplacer des variables (modifiables) d'une classe à une autre (ainsi que de déplacer des variables (modifiables) de l'extérieur d'une boucle à l'intérieur) présente un avantage important, en ce sens qu'il simplifie le raisonnement du contrôle-flux.

En revanche, éliminer les variables comme dans votre dernier exemple présente moins d'avantages, car cela n'affecte pas vraiment le raisonnement du contrôle-flux. Vous perdez également les noms attribués aux valeurs, ce qui ne se produit pas lorsque vous déplacez simplement une variable dans une portée interne. C’est un compromis que vous devez prendre en compte. Par conséquent, l’élimination des variables peut être meilleure dans certains cas et pire dans d’autres.

Si vous ne voulez pas perdre les noms de variables, mais que vous voulez tout de même réduire la portée des variables (si elles sont utilisées dans une fonction plus grande), vous pouvez envisager de regrouper les variables et leurs utilisations dans une instruction block ( ou les déplacer à leur propre fonction ).

YawarRaza7349
la source
2

Cela dépend un peu de la langue, mais je dirais que l’un des avantages moins évidents de la programmation fonctionnelle est qu’elle encourage le programmeur et le lecteur de code à ne pas en avoir besoin. Considérer:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

Ou un peu de LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

Ou Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

La dernière est une chaîne d'appels d'une fonction sur le résultat d'une fonction précédente, sans aucune variable intermédiaire. Leur présentation le rendrait beaucoup moins clair.

Cependant, la différence entre le premier exemple et les deux autres correspond à l' ordre implicite de fonctionnement . Ce n'est peut-être pas le même ordre que l'ordre dans lequel il a été calculé, mais c'est l'ordre dans lequel le lecteur devrait y réfléchir. Pour les deux autres, cela va de gauche à droite. Pour l'exemple Lisp / Clojure, c'est plus comme de droite à gauche. Vous devriez vous méfier de l'écriture de code qui n'est pas dans la "direction par défaut" de votre langue, et les expressions de "moyen terme" qui mélangent les deux doivent absolument être évitées.

L’opérateur de pipe de F # |>est utile en partie parce qu’il vous permet d’écrire des choses de gauche à droite qui devraient sinon être de droite à gauche.

pjc50
la source
2
J'ai commencé à utiliser le trait de soulignement comme nom de variable dans de simples expressions LINQ ou lambdas. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham
Je ne sais pas ce à la question de réponses OP dans le moindre ...
AC
1
Pourquoi les votes négatifs? Cela me semble être une bonne réponse, même si cela ne répond pas directement à la question, cela reste des informations utiles
Reggaeguitar
1

Je dirais non, car vous devriez lire "la plus petite portée possible" comme "parmi les portées existantes ou celles qu'il est raisonnable d'ajouter". Sinon, cela impliquerait que vous créiez des portées artificielles (par exemple, des {}blocs gratuits dans des langages de type C) juste pour vous assurer que la portée d'une variable ne dépasse pas la dernière utilisation prévue, et que cela serait généralement mal vu, à moins qu'il n'y ait déjà une bonne raison pour que le champ existe indépendamment.

R ..
la source
2
Un problème avec la portée dans de nombreuses langues est qu’il est extrêmement courant d’avoir certaines variables dont le dernier usage est le calcul des valeurs initiales d’autres variables. Si un langage comportait explicitement des constructions à des fins de cadrage, ce qui permettait d'utiliser des valeurs calculées à l'aide de variables de portée interne lors de l'initialisation de variables étendues, de telles portées pourraient être plus utiles que les portées strictement imbriquées requises par de nombreux langages.
Supercat
1

Considérons les fonctions ( méthodes ). Il ne divise pas le code dans la plus petite sous-tâche possible, ni le plus gros morceau de code singleton.

Il s'agit d'une limite de déplacement, avec délimitation de tâches logiques, en éléments consommables.

La même chose vaut pour les variables . Mise en évidence des structures de données logiques, dans des parties compréhensibles. Ou aussi simplement nommer (énoncer) les paramètres:

boolean automatic = true;
importFile(file, automatic);

Mais bien sûr, avec une déclaration en haut et deux cents lignes plus loin, le premier usage est de nos jours considéré comme un mauvais style. C’est clairement ce que «les variables doivent vivre dans le plus petit champ possible», entend-on dire. Comme un très proche "ne pas réutiliser les variables."

Joop Eggen
la source
1

Ce qui manque quelque peu en tant que raison pour NO est le débogage / la capacité de lecture. Le code doit être optimisé pour cela, et des noms clairs et concis aident beaucoup, par exemple, imaginez trois façons si

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

cette ligne est courte, mais déjà difficile à lire. Ajoutez quelques paramètres supplémentaires, et cela si s'étend sur plusieurs lignes.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Je trouve cette manière plus facile de lire et de communiquer le sens - je n’ai donc aucun problème avec les variables intermédiaires.

Un autre exemple serait celui de langues telles que R, où la dernière ligne est automatiquement la valeur de retour:

some_func <- function(x) {
    compute(x)
}

c'est dangereux, le retour est-il prolongé ou nécessaire? c'est plus clair:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Comme toujours, il s’agit d’un jugement: éliminez les variables intermédiaires si elles n’améliorent pas la lecture, sinon conservez-les ou introduisez-les.

La débogage est un autre point: si les résultats intermédiaires sont intéressants, il peut être préférable d’introduire simplement un intermédiaire, comme dans l’exemple R ci-dessus. Il est difficile d'imaginer combien de fois cela est nécessaire et faites attention à ce que vous archivez - trop de variables de débogage prêtent à confusion - encore une fois, un jugement.

Christian Sauer
la source
0

En se référant uniquement à votre titre: absolument, si une variable est inutile, elle doit être supprimée.

Mais «inutile» ne signifie pas qu'un programme équivalent peut être écrit sans utiliser la variable, sinon on nous dirait qu'il faut tout écrire en binaire.

Le type le plus courant de variable non nécessaire est une variable non utilisée. Plus la portée de la variable est petite, plus il est facile de déterminer qu’elle est inutile. Il est plus difficile de déterminer si une variable intermédiaire est inutile, car il ne s'agit pas d'une situation binaire, mais contextuelle. En fait, un code source identique dans deux méthodes différentes pourrait produire une réponse différente par le même utilisateur, en fonction de l'expérience acquise en matière de résolution de problèmes dans le code environnant.

Si votre code d'exemple était exactement tel qu'il était représenté, je vous suggérerais de vous débarrasser des deux méthodes privées, sans vous soucier de savoir si vous avez enregistré le résultat des appels d'usine à une variable locale ou si vous les avez simplement utilisés comme arguments du mélange. méthode.

La lisibilité du code prime sur tout sauf sur le fait de fonctionner correctement (inclut correctement les critères de performance acceptables, qui sont rarement «aussi rapides que possible»).

jmoreno
la source