web-dev-qa-db-fra.com

Pourquoi éviter les opérateurs d'incrémentation ("++") et de décrémentation ("-") en JavaScript?

L'un des astuces pour l'outil jslint est:

++ et -
On sait que les opérateurs ++ (incrément) et - (décrément) contribuent au mauvais code en encourageant les trucs excessifs. Ils ne sont en second lieu que par une architecture défaillante en ce qui concerne les virus et autres menaces de sécurité. Il existe une option plusplus qui interdit l'utilisation de ces opérateurs.

Je sais que PHP des constructions telles que $foo[$bar++] peuvent générer facilement des erreurs uniques, mais je ne pouvais pas trouver un meilleur moyen de contrôler la boucle qu'un while( a < 10 ) do { /* foo */ a++; } ou for (var i=0; i<10; i++) { /* foo */ }.

Est-ce que jslint les met en surbrillance car il existe des langages similaires qui ne possèdent pas la syntaxe "++" et "--" ou la gèrent différemment, ou existe-t-il d'autres raisons d'éviter "++" et "--" que je pourrais être absent?

345
artlung

Mon point de vue est de toujours utiliser ++ et - par eux-mêmes sur une seule ligne, comme dans:

i++;
array[i] = foo;

au lieu de

array[++i] = foo;

Tout ce qui va au-delà peut être déroutant pour certains programmeurs et, à mon avis, n'en vaut pas la peine. Les boucles for sont une exception, car l’utilisation de l’opérateur d’incrément est idiomatique et donc toujours claire.

395
cdmckay

Je suis franchement confus par ce conseil. Une partie de moi se demande s’il s’agit davantage d’un manque d’expérience (perçue ou réelle) avec les codeurs javascript.

Je peux voir comment quelqu'un qui "pirate" simplement quelques exemples de code pourrait faire une erreur innocente avec ++ et -, mais je ne vois pas pourquoi un professionnel expérimenté les éviterait.

249
Jon B

Il y a une histoire en C de faire des choses comme:

while (*a++ = *b++);

copier une chaîne est peut-être à l'origine de la supercherie à laquelle il fait allusion.

Et il y a toujours la question de savoir quoi

++i = i++;

ou

i = i++ + ++i;

fait réellement. Il est défini dans certaines langues et dans d'autres, rien ne garantit ce qui va arriver.

Mis à part ces exemples, je ne pense pas qu'il y ait plus idiomatique qu'une boucle for qui utilise ++ pour incrémenter. Dans certains cas, vous pourriez vous en tirer avec une boucle foreach ou une boucle while vérifiant une condition différente. Mais contourner votre code pour éviter d'utiliser l'incrémentation est ridicule.

69
Eclipse

Si vous lisez JavaScript The Good Parts, vous verrez que le remplacement de Crockford pour i ++ dans une boucle pour est i + = 1 (et non i = i + 1). C'est assez propre et lisible, et est moins susceptible de se transformer en quelque chose de "difficile".

Crockford a désactivé l'auto-incrémentation et le décodage automatique d'une option dans jsLint. Vous choisissez de suivre les conseils ou non.

Ma règle personnelle est de ne rien faire combiné avec l'auto-incrémentation ou l'autodécrément.

Des années d'expérience en langage C m'ont appris que je n'éprouve pas de dépassement de tampon (ou d'index de tableau en dehors des limites) si je l'utilise simplement. Mais j’ai découvert que j’obtenais effectivement des dépassements de mémoire tampon si je tombais dans la pratique "excessivement délicate" de faire d’autres choses dans la même déclaration.

Donc, pour mes propres règles, l’utilisation de i ++ en tant qu’incrément dans une boucle pour convient.

45
Nosredna

Dans une boucle, c'est inoffensif, mais dans une déclaration de mission, cela peut conduire à des résultats inattendus:

var x = 5;
var y = x++; // y is now 5 and x is 6
var z = ++x; // z is now 7 and x is 7

Les espaces entre la variable et l'opérateur peuvent également entraîner des résultats inattendus:

a = b = c = 1; a ++ ; b -- ; c; console.log('a:', a, 'b:', b, 'c:', c)

Dans une fermeture, des résultats inattendus peuvent également poser problème:

var foobar = function(i){var count = count || i; return function(){return count++;}}

baz = foobar(1);
baz(); //1
baz(); //2


var alphabeta = function(i){var count = count || i; return function(){return ++count;}}

omega = alphabeta(1);
omega(); //2
omega(); //3

Et cela déclenche l’insertion automatique de points-virgules après une nouvelle ligne:

var foo = 1, bar = 2, baz = 3, alpha = 4, beta = 5, delta = alpha
++beta; //delta is 4, alpha is 4, beta is 6

une confusion pré-incrémentation/post-incrémentation peut produire des erreurs uniques, extrêmement difficiles à diagnostiquer. Heureusement, ils sont également complètement inutiles. Il existe de meilleurs moyens d’ajouter 1 à une variable.

Références

26
Paul Sweatte

Considérons le code suivant

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[i++] = i++;
    a[i++] = i++;
    a[i++] = i++;

puisque i ++ est évalué deux fois, le résultat est (à partir du débogueur vs2005)

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Considérons maintenant le code suivant:

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = ++i;
    a[++i] = ++i;
    a[++i] = ++i;

Notez que la sortie est la même. Maintenant, vous pourriez penser que ++ i et i ++ sont identiques. Ils ne sont pas

    [0] 0   int
    [1] 0   int
    [2] 2   int
    [3] 0   int
    [4] 4   int

Enfin considérez ce code

    int a[10];
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = 0;
    int i = 0;
    a[++i] = i++;
    a[++i] = i++;
    a[++i] = i++;

La sortie est maintenant:

    [0] 0   int
    [1] 1   int
    [2] 0   int
    [3] 3   int
    [4] 0   int
    [5] 5   int

Donc, ils ne sont pas identiques, mélanger les deux entraîne un comportement moins intuitif. Je pense que les boucles for vont bien avec ++, mais faites attention quand vous avez plusieurs symboles ++ sur la même ligne ou la même instruction

17
Eric

La nature "pré" et "post" des opérateurs d'incrémentation et de décrémentation peut être source de confusion pour ceux qui ne les connaissent pas bien; c'est une façon par laquelle ils peuvent être difficiles.

16
Paul Sonier

À mon avis, "Explicite vaut toujours mieux qu'implicite." Parce qu'à un moment donné, vous risquez d'être confondu avec cette instruction d'incrémentation y+ = x++ + ++y. Un bon programmeur rend toujours son code plus lisible.

16
Sriram

La raison la plus importante pour éviter ++ ou - est que les opérateurs renvoient des valeurs et provoquent des effets secondaires en même temps, ce qui complique la tâche de raisonner à propos du code.

Par souci d'efficacité, je préfère:

  • ++ i quand ne pas utiliser la valeur de retour (pas temporaire)
  • i ++ quand tiliser la valeur de retour (pas de décrochage du pipeline)

Je suis un fan de M. Crockford, mais dans ce cas, je ne suis pas d'accord. ++i contient 25% moins de texte à analyser que i+=1 et sans doute plus clair.

13
Hans Malherbe

J'ai regardé la vidéo de Douglas Crockford à ce sujet et son explication pour ne pas utiliser incrément et décrément est que

  1. Il a été utilisé dans d’autres langues dans le passé pour casser les limites des tableaux et provoquer toutes les manières du mal et des
  2. Les développeurs JS, plus confus et inexpérimentés, ne savent pas exactement ce que ça fait.

Premièrement, les tableaux en JavaScript sont dimensionnés de manière dynamique et, pardonnez-moi si je me trompe, il n'est pas possible de casser les limites d'un tableau et d'accéder aux données qui ne devraient pas être accessibles à l'aide de cette méthode en JavaScript.

Deuxièmement, si nous évitons les choses compliquées, le problème n’est certes pas le fait de disposer de cette installation, mais bien le fait qu’il existe des développeurs qui prétendent utiliser JavaScript mais ne savent pas comment ces opérateurs fonctionnent. C'est assez simple. value ++, donnez-moi la valeur actuelle et, après l'expression, ajoutez-en un, ++ valeur, incrémentez la valeur avant de me le donner.

Des expressions comme a ++ + ++ b sont faciles à résoudre si vous vous souvenez de ce qui précède.

var a = 1, b = 1, c;
c = a ++ + ++ b;
// c = 1 + 2 = 3; 
// a = 2 (equals two after the expression is finished);
// b = 2;

Je suppose que vous devez juste vous rappeler qui doit lire le code. Si vous avez une équipe qui connaît JS ​​de fond en comble, vous n'avez pas à vous inquiéter. Sinon, commentez-le, écrivez-le différemment, etc. Faites ce que vous devez faire. Je ne pense pas que l'incrémentation et la décrémentation soient intrinsèquement mauvaises, ou génératrices de bogues ou de vulnérabilités, peut-être tout simplement moins lisibles en fonction de votre public.

Au fait, je pense que Douglas Crockford est une légende de toute façon, mais je pense qu'il a fait peur à un opérateur qui ne le méritait pas.

Je vis pour prouver que j'ai tort.

13
Stuart Wakefield

Un autre exemple, plus simple que d’autres, avec un simple retour de valeur incrémentée:

function testIncrement1(x) {
    return x++;
}

function testIncrement2(x) {
    return ++x;
}

function testIncrement3(x) {
    return x += 1;
}

console.log(testIncrement1(0)); // 0
console.log(testIncrement2(0)); // 1
console.log(testIncrement3(0)); // 1

Comme vous pouvez le constater, aucune instruction post-incrémentation/décrémentation ne doit être utilisée dans l'instruction return si vous souhaitez que cet opérateur influence le résultat. Mais return ne "capture" pas les opérateurs de post-incrémentation/décrémentation:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}
10
Evgeny Levin

Je pense que les programmeurs devraient être compétents dans le langage qu'ils utilisent; utilisez-le clairement; et bien l'utiliser. Je non pense qu'ils devraient artificiellement paralyser le langage qu'ils utilisent. Je parle d'expérience. J'ai déjà travaillé littéralement à côté d'un magasin Cobol où ils n'utilisaient pas ELSE "parce que c'était trop compliqué". Reductio ad absurdam.

8
user207421

Mon 2cents est qu'ils devraient être évités dans deux cas:

1) Lorsque vous avez une variable utilisée dans plusieurs lignes et que vous l'augmentez/la diminuez à la première instruction qui l'utilise (ou la dernière, ou pire encore, au milieu):

// It's Java, but applies to Js too
vi = list.get ( ++i );
vi1 = list.get ( i + 1 )
out.println ( "Processing values: " + vi + ", " + vi1 )
if ( i < list.size () - 1 ) ...

Dans des exemples comme celui-ci, vous pouvez facilement oublier que la variable est auto-incrémentée/décrémentée ou même supprimer la première instruction. En d'autres termes, utilisez-le uniquement dans des blocs très courts ou lorsque la variable apparaît dans le bloc dans seulement deux instructions proches.

2) Dans le cas de plusieurs ++ et - environ la même variable dans la même instruction. Il est très difficile de se rappeler ce qui se passe dans des cas comme celui-ci:

result = ( ++x - --x ) * x++;

Les examens et les tests professionnels demandent des exemples comme ci-dessus et, en fait, je suis tombé sur cette question alors que je cherchais de la documentation sur l'un d'entre eux, mais dans la vraie vie, il ne faut pas être obligé de penser autant à une seule ligne de code.

2
zakmck

D'après mon expérience, ++ i ou i ++ n'a jamais causé de confusion autre que lors du premier apprentissage du fonctionnement de l'opérateur. Il est essentiel pour les bases les plus élémentaires pour les boucles et les boucles while qui sont enseignées par tous les cours de lycée ou de collège enseignés dans des langues dans lesquelles vous pouvez utiliser l’opérateur. Personnellement, je trouve que faire quelque chose comme ce qui est en dessous pour regarder et lire mieux que quelque chose avec un ++ étant sur une ligne séparée.

while ( a < 10 ){
    array[a++] = val
}

En fin de compte, c'est une préférence de style et rien de plus. Ce qui est plus important, c'est que lorsque vous faites cela dans votre code, vous restiez cohérent afin que les autres personnes travaillant sur le même code puissent suivre et ne pas avoir à traiter la même fonctionnalité de différentes manières. .

De plus, Crockford semble utiliser i- = 1, que je trouve plus difficile à lire que --i ou i--

2
burdz

Je ne sais pas si cela faisait partie de son raisonnement, mais si vous utilisez un programme de minification mal écrit, il pourrait transformer x++ + y en x+++y. Mais là encore, un outil mal écrit peut causer toutes sortes de ravages.

2
James M.

Comme mentionné dans certaines des réponses existantes (ce que je ne peux pas commenter ennuyeux), le problème est que x ++ ++ x évalue des valeurs différentes (avant vs après l'incrément), ce qui n'est pas évident et peut être très déroutant - if cette valeur est utilisée. cdmckay suggère assez judicieusement d'autoriser l'utilisation de l'opérateur d'incrémentation, mais uniquement de manière à ne pas utiliser la valeur renvoyée, par ex. sur sa propre ligne. J'inclurais également l'utilisation standard dans une boucle for (mais uniquement dans la troisième instruction, dont la valeur de retour n'est pas utilisée). Je ne peux pas penser à un autre exemple. Ayant moi-même été "brûlé", je recommanderais la même directive pour les autres langues.

Je ne suis pas d'accord avec l'affirmation selon laquelle cette trop grande rigueur est due à l'inexpérience de nombreux programmeurs de JS. C’est exactement le type d’écriture typique des programmeurs "trop ​​intelligents", et je suis sûr que c’est beaucoup plus courant dans les langages plus traditionnels et chez les développeurs JS qui ont une expérience dans ces langages.

2
Near Privman

Le Fortran est-il une langue semblable au C? Il n'a ni ++ ni -. Voici comment vous écrivez une boucle :

     integer i, n, sum

      sum = 0
      do 10 i = 1, n
         sum = sum + i
         write(*,*) 'i =', i
         write(*,*) 'sum =', sum
  10  continue

L'élément d'index i est incrémenté des règles de langage à chaque fois dans la boucle. Si vous voulez incrémenter de quelque chose d'autre que 1, comptez deux fois en arrière, par exemple, la syntaxe est la suivante ...

      integer i

      do 20 i = 10, 1, -2
         write(*,*) 'i =', i
  20  continue

Est-ce que Python C-like? Il utilise les interprétations de la plage et et d'autres syntaxes pour contourner le besoin de incrémenter un index:

print range(10,1,-2) # prints [10,8.6.4.2]
[x*x for x in range(1,10)] # returns [1,4,9,16 ... ]

Ainsi, en se basant sur cette exploration rudimentaire de exactement deux alternatives, les concepteurs de langage peuvent éviter ++ et - en anticipant les cas d'utilisation et en fournissant une syntaxe alternative.

Fortran et Python sont-ils sensiblement moins un aimant des bogues que les langages procéduraux dotés de ++ et -? Je n'ai aucune preuve.

Je prétends que Fortran et Python ressemblent à C parce que je n'ai jamais rencontré quelqu'un qui parle couramment C et qui ne pourrait pas, avec une précision de 90%, deviner correctement l'intention de Fortran ou de Python non obfusqués.

1
Thomas L Holaday