¿Por qué evitar operadores de incremento ("++") y decremento ("-") en JavaScript?

357

Uno de los consejos para la herramienta jslint es:

++ y -
Se sabe que los operadores ++ (incremento) y - (decremento) contribuyen al mal código al alentar trucos excesivos. Son los segundos a la arquitectura defectuosa al permitir virus y otras amenazas de seguridad. Hay una opción plusplus que prohíbe el uso de estos operadores.

Sé que las construcciones PHP como $foo[$bar++]has pueden resultar fácilmente en errores fuera de uno, pero no pude encontrar una mejor manera de controlar el ciclo que unwhile( a < 10 ) do { /* foo */ a++; } o for (var i=0; i<10; i++) { /* foo */ }.

¿Está jslint resaltándolos porque hay algunos lenguajes similares que carecen de la sintaxis " ++" y " --" o lo manejan de manera diferente, o hay otras razones para evitar " ++" y " --" que podría estar perdiendo?

artlung
fuente
59
Por lo tanto, uno debe hacer array [index = index + 1] en lugar de array [++ index] (¡si el primero está permitido!). Qué carga de hooey.
Lawrence Dol
34
No he visto Crockford do index = index + 1. Lo he visto hacer index + = 1. Creo que es una alternativa razonable. Y es bueno para cuando quieres cambiar el paso a algo además de 1.
Nosredna
124
Personalmente no soy un gran admirador de Crockford. Parece considerar que todo lo que haya causado un error en su código es malo.
Paolo Bergantino
38
En JavaScript, debe considerar cada error como algo malvado, ya que no hay documentación oficial ni hay un proveedor de certificados ni aprende JS correctamente en la Universidad. Crockford y Firebug han llenado estos agujeros en la educación de JavaScript.
stevek
40
++No causa errores. El uso ++de formas "complicadas" puede provocar errores, especialmente si más de una persona mantiene la base de código, pero eso no es un problema con el operador, es un problema con el programador. No aprendí JS en la universidad (porque aún no existía), pero ¿y qué? Hice C, que por supuesto tenía ++primero, pero eso también tiene un "¿y qué?" No fui a la universidad para aprender un idioma específico, fui a aprender buenas prácticas de programación que puedo aplicar a cualquier idioma.
nnnnnn

Respuestas:

408

Mi opinión es usar siempre ++ y - por sí mismos en una sola línea, como en:

i++;
array[i] = foo;

en vez de

array[++i] = foo;

Cualquier cosa más allá de eso puede ser confusa para algunos programadores y, en mi opinión, simplemente no vale la pena. Los bucles son una excepción, ya que el uso del operador de incremento es idiomático y, por lo tanto, siempre está claro.

cdmckay
fuente
3
Sí, eso es lo que yo hago también. Es difícil equivocarse y es más fácil para otras personas darse cuenta de lo que está haciendo.
Nosredna
65
Eso parece estar llegando al núcleo del problema y mi confusión. Usado solo, i ++; Está claro como el cristal. En el momento en que agrega otro código alrededor de esa expresión básica, la legibilidad y la claridad comienzan a sufrir.
artlung
2
De acuerdo, pero no solo está relacionado con JavaScript
Tobias
55
No dice si está escribiendo C o C ++. Podría decirse que debe usar el operador de incremento de prefijo en C ++, en caso de que la ivariable luego se convierta en una clase compuesta, en cuyo caso estaría creando innecesariamente un objeto temporal. Sin embargo, el operador de postfix me parece más estéticamente agradable.
mc0e
2
Prefiero la versión de 1 línea. Recuerda una operación de pila. push es array [++ i] = foo, pop es bar = array [i--]. (Pero con matrices basadas en 0, array [i ++] = foo y bar = array [- i] se ven aún mejor). Otra cosa, odiaría si alguien agregara código adicional entre i ++; y matriz [i] = foo ;.
Florian F
257

Estoy francamente confundido por ese consejo. Parte de mí se pregunta si tiene más que ver con la falta de experiencia (percibida o real) con los codificadores de JavaScript.

Puedo ver cómo alguien que simplemente "piratea" algún código de muestra podría cometer un error inocente con ++ y -, pero no veo por qué un profesional experimentado los evitaría.

Jon B
fuente
77
Buen punto Jon B. Por eso me molesta tanto. Crockford es un programador de carrera (valorado en décadas), conoce varios idiomas a lo largo de décadas y trabaja con JavaScript básicamente desde su inicio. No creo que su consejo provenga de "Soy un hacker perezoso y desatento sin experiencia", es por eso que estoy tratando de entender de dónde viene.
artlung
14
@artlung Quizás tenga más que ver con "Un hacker perezoso y desatento sin experiencia" probablemente se meterá con este código, y no quiero confundirlo.
Chris Cudmore
99
Estoy completamente en desacuerdo con tu respuesta. En mi opinión, no se trata de '¿Tengo suficiente experiencia para usarlo?' - pero '¿Está más claro o no?' Si dentro de un bucle for, o solo, está claro como el cristal, todos lo entenderán sin ningún daño. El problema surge cuando se pone ++una expresión más enrevesada en la que ++ x es diferente de x ++, lo que resulta en algo que no es fácil de leer. La idea de Crockford no se trata de "¿puedo hacerlo?" se trata de '¿cómo puedo evitar errores?'
ArtoAle
1
Es la preferencia personal en qué punto la claridad es más importante para usted que la compacidad. Esto va más allá de tener la experiencia suficiente para entender el código. Vea otras respuestas para ver ejemplos en los que es más problemático de lo que vale.
Frug
1
Estoy totalmente de acuerdo. ¿Por qué perjudicarse al no usar parte del lenguaje? Encuentro buenas razones para usar tanto incrementos como decrementos posteriores y previos. Para mí, una línea como esta es clara y concisa ... if (--imagesLoading === 0) start (); Si cree que su código no está claro, ¡use comentarios!
xtempore
69

Hay una historia en C de hacer cosas como:

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

para copiar una cadena, tal vez esta sea la fuente del engaño excesivo al que se refiere.

Y siempre está la cuestión de qué

++i = i++;

o

i = i++ + ++i;

En realidad hacer. Está definido en algunos idiomas, y en otros no hay garantía de lo que sucederá.

Dejando a un lado esos ejemplos, no creo que haya nada más idiomático que un bucle for que se usa ++para incrementar. En algunos casos, puede salirse con un bucle foreach o un bucle while que verifica una condición diferente. Pero torcer su código para tratar de evitar el uso de incrementos es ridículo.

Eclipse
fuente
104
i = i ++ + ++ i; hizo que mis ojos duelen un poco - :)
Hugoware
3
Mío también. Incluso si no fueran todas la misma variable, digamos que tenía x = a ++ + ++ b; - esa combinación al instante hace que x, ayb sean más difíciles de mantener en mi cabeza. ahora, si cada uno fuera enunciados separados en líneas separadas, como en - a ++; ++ b; x = a + b; Sería más fácil de comprender a primera vista.
artlung
10
Por desgracia, (a ++; b ++; x = a = b) no es el mismo valor que (a ++ + ++ b).
Thomas L Holaday
8
@ Mario: x = a+++b-> x = (a++)+b-> x = a + b; a++. El tokeniser es codicioso.
Callum Rogers
14
Para mayor seguridad laboral, elimine los espacios en su último ejemplo.
mc0e
48

Si lee JavaScript The Good Parts, verá que el reemplazo de Crockford para i ++ en un bucle for es i + = 1 (no i = i + 1). Eso es bastante limpio y legible, y es menos probable que se convierta en algo "complicado".

Crockford hizo que no permitir el autoincremento y el autodecremento sea una opción en jsLint. Tú eliges si seguir los consejos o no.

Mi propia regla personal es no hacer nada combinado con autoincremento o autodecremento.

Aprendí de años de experiencia en C que no obtengo desbordamientos del búfer (o índice de matriz fuera de los límites) si mantengo el uso simple. Pero he descubierto que obtengo desbordamientos del búfer si caigo en la práctica "excesivamente complicada" de hacer otras cosas en la misma declaración.

Entonces, para mis propias reglas, el uso de i ++ como el incremento en un bucle for está bien.

Nosredna
fuente
Excelente punto acerca de que la opción "plusplus" es solo eso, una opción. Basado en las suyas y otras respuestas, creo que tengo la sensación de que ++ por sí mismo o un bucle for no son confusos en sí mismos. En el segundo que combina ese ++ en otra declaración, su declaración se vuelve más difícil de leer y entender en contexto.
artlung
66
todos reciben desbordamientos del búfer. Incluso OpenSSH con su código abierto y sus múltiples comités de revisión
Eric
11
@Eric Revisitando tu comentario de cinco años: ¡oh, qué razón tenías!
wchargin
27

En un bucle es inofensivo, pero en una declaración de asignación puede conducir a resultados inesperados:

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

El espacio en blanco entre la variable y el operador también puede generar resultados inesperados:

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

En un cierre, los resultados inesperados también pueden ser un problema:

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

Y desencadena la inserción automática de punto y coma después de una nueva línea:

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

La confusión preincremento / postincremento puede producir errores ocasionales que son extremadamente difíciles de diagnosticar. Afortunadamente, también son completamente innecesarios. Hay mejores formas de agregar 1 a una variable.

Referencias

Paul Sweatte
fuente
13
Pero no son "inesperados" si entiendes al operador. Es como no usarlo nunca ==porque no entiendes la diferencia entre ==y ===.
greg84
1
Exactamente. Hay una pregunta de C # que desmiente las suposiciones falsas.
Paul Sweatte
Creo que el punto de Crockford es que la mayoría de los programadores no "entienden completamente al operador" incluso cuando piensan que sí; por lo tanto, existe el peligro de usarlos porque el potencial de malentendidos es alto. Vea el comentario de @ Paul sobre suposiciones falsas que refuerza el argumento de que las personas generalmente no entienden cómo funcionan realmente los operadores de prefijos y postfijos. Dicho esto, admito que me gusta usarlos, pero por el bien de otros trato de limitar su uso a casos idiomáticos (ver la respuesta de cdmckay ).
gfullam
Su enlace de espacios en blanco parece estar roto.
Ruslan
¿Qué tiene de complicado tu ejemplo de "espacio en blanco"? Obtengo una salida directa de a: 2 b: 0 c: 1. Tampoco veo nada extraño o inesperado en el primer ejemplo ("declaración de asignación").
Ken Williams
17

Considere el siguiente código

    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++;

dado que i ++ se evalúa dos veces, el resultado es (del depurador vs2005)

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

Ahora considere el siguiente código:

    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;

Tenga en cuenta que la salida es la misma. Ahora puede pensar que ++ i e i ++ son lo mismo. No son

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

Finalmente considere este código

    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 salida es ahora:

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

Por lo tanto, no son lo mismo, mezclar ambos resulta en un comportamiento no tan intuitivo. Creo que los bucles for están bien con ++, pero ten cuidado cuando tienes varios símbolos ++ en la misma línea o la misma instrucción

Eric
fuente
3
Gracias por hacer ese experimento. En particular, el código de "aspecto simple", pero es difícil para mi cerebro retenerlo muy rápidamente. Definitivamente cae en la categoría "complicado".
artlung
28
Paré después del primer ejemplo. Dijiste "Considera el siguiente código". No, nadie escribe ese código. Es el idiota quien escribe que es defectuoso, no una construcción del lenguaje.
Sam Harwell el
44
¿Ha demostrado que dos códigos diferentes producen resultados diferentes y esto demuestra que es malo?
nickf
77
la conclusión es "ten cuidado cuando tienes múltiples símbolos ++ en la misma línea o la misma instrucción". No creo que lo hayas leído
Eric
1
"ya que i ++ se evalúa dos veces" - ¡mal! Modificar una variable dos veces entre puntos de secuencia y también usarla no es válido en C ++ y conduce a un comportamiento indefinido. Entonces el compilador puede hacer lo que quiera. Los resultados presentados aquí son accidentales de la implementación del compilador.
tbleher
16

La naturaleza "previa" y "posterior" de los operadores de incremento y decremento puede tender a ser confusa para quienes no están familiarizados con ellos; esa es una forma en que pueden ser complicados.

Paul Sonier
fuente
29
Convenido; Sin embargo, un profesional con experiencia debe no confundirse.
Nate
1
Creo que esto es similar a la guía para evitar que todo sea estático o global. No hay nada intrínsecamente malo en ello como una construcción de programación, pero el uso excesivo ignorante puede conducir a un código incorrecto.
Joel Martinez
66
La pregunta no es si un buen programador podría "abrirse camino" a través de la lógica; idealmente, un buen código no requiere ningún trabajo para comprenderlo. Creo que el punto de McWafflestix es que no deberías escribir un código que después de una breve inspección podría conducir a la adición de errores en dos semanas o dos años. Sé que he sido culpable de añadir código a una rutina que no entendía totalmente :)
Mike
1
@ Mike: sí, observa cómo no especifiqué si los operadores pueden ser complicados para el autor original del código o para los encargados posteriores. En general, tratamos de suponer que los codificadores originales no usan construcciones con las que no están familiarizados / cómodos (lamentablemente, esto es con frecuencia una suposición incorrecta); sin embargo, esa familiaridad ciertamente no se extiende a los mantenedores (y, como se señaló entre paréntesis anteriormente, puede que ni siquiera se extienda al autor original).
Paul Sonier
Intento escribir mi código para que lo lean otros programadores, pero no intento escribirlo para que los principiantes lo lean.
Luke H
16

En mi opinión, "explícito siempre es mejor que implícito". Porque en algún momento, puede confundirse con esta declaración de incrementos y+ = x++ + ++y. Un buen programador siempre hace que su código sea más legible.

Sriram
fuente
1
No hay nada implícito en x ++ o ++ y.
Florian F
1
Este código legible es complicado ... Creo que hay una línea de base para lo simple que debe ser su código, y nosotros, como comunidad, debemos prosperar un poco y poder leer lo que ahora no es legible, como las frases sencillas. con terneros anidados, para permitir que más cosas
entren
14

La razón más importante para evitar ++ o - es que los operadores devuelven valores y causan efectos secundarios al mismo tiempo, lo que hace que sea más difícil razonar sobre el código.

Por el bien de la eficiencia, prefiero:

  • ++ i cuando no se utiliza el valor de retorno (no temporal)
  • i ++ cuando se usa el valor de retorno (sin bloqueo de canalización)

Soy fanático del Sr. Crockford, pero en este caso tengo que estar en desacuerdo. ++ies un 25% menos de texto para analizar i+=1 y posiblemente más claro.

Hans Malherbe
fuente
13

He estado viendo el video de Douglas Crockford sobre esto y su explicación para no usar incrementos y decrementos es que

  1. Se ha utilizado en el pasado en otros idiomas para romper los límites de las matrices y causar todas las formas de maldad y
  2. Que es más confuso e inexperto los desarrolladores de JS no saben exactamente lo que hace.

En primer lugar, las matrices en JavaScript tienen un tamaño dinámico y, por lo tanto, perdóneme si me equivoco, no es posible romper los límites de una matriz y acceder a datos a los que no se debe acceder utilizando este método en JavaScript.

En segundo lugar, si evitamos las cosas que son complicadas, seguramente el problema no es que tengamos esta instalación, sino que hay desarrolladores por ahí que dicen hacer JavaScript pero no saben cómo funcionan estos operadores. Es lo suficientemente simple. valor ++, dame el valor actual y después de la expresión agrégale uno, valor ++, incrementa el valor antes de darme.

Expresiones como a ++ + ++ b, son fáciles de resolver si solo recuerda lo anterior.

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

Supongo que solo tienes que recordar quién tiene que leer el código, si tienes un equipo que conoce JS al revés, entonces no debes preocuparte. Si no, entonces coméntelo, escríbalo de manera diferente, etc. Haga lo que tiene que hacer. No creo que el incremento y la disminución sean inherentemente malos o generen errores, o creen vulnerabilidades, tal vez solo sean menos legibles dependiendo de su audiencia.

Por cierto, creo que Douglas Crockford es una leyenda de todos modos, pero creo que ha causado mucho miedo por un operador que no lo merecía.

Aunque vivo para demostrar que estoy equivocado ...

Stuart Wakefield
fuente
10

Otro ejemplo, más simple que otros con un simple retorno de valor incrementado:

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

Como puede ver, no debe usarse ningún incremento / decremento posterior en la declaración de devolución, si desea que este operador influya en el resultado. Pero return no "atrapa" a los operadores de incremento / decremento posteriores:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}
Evgeny Levin
fuente
Solo una de sus funciones recibe x como parámetro
Calimero100582
8

Creo que los programadores deberían ser competentes en el lenguaje que están usando; úsalo claramente; y úsalo bien. Yo no creo que deberían paralizar artificialmente el lenguaje que están utilizando. Yo hablo por experiencia. Una vez trabajé literalmente al lado de una tienda de Cobol donde no usaban ELSE "porque era demasiado complicado". Reductio ad absurdam.

Marqués de Lorne
fuente
@downvoter Fascinante. ¿No crees que los programadores deberían ser competentes en el idioma? ¿Crees que deberían paralizar artificialmente el lenguaje? Cual es No hay una tercera opción aquí.
Marqués de Lorne
1
y un voto a favor para ese ejemplo de COBOL, me alegra aún más de que COBOL se haya extinguido antes de que empezara a codificar
Mark K Cowan
1
@ MarkCowan No veo por qué. Mi punto es sobre la restricción arbitraria, no el idioma. La anécdota no indica nada malo con Cobol. Lo que necesitaba desaparecer era esa tienda de programación, y lo hizo. Visite las entrañas de cualquier banco grande y encontrará que Cobol está vivo y bien.
Marqués de Lorne
2

Como se menciona en algunas de las respuestas existentes (que molestamente no puedo comentar), el problema es que x ++ ++ x evalúa a diferentes valores (antes vs después del incremento), lo cual no es obvio y puede ser muy confuso. Si se usa ese valor. cdmckay sugiere sabiamente permitir el uso del operador de incremento, pero solo de manera que el valor devuelto no se use, por ejemplo, en su propia línea. También incluiría el uso estándar dentro de un ciclo for (pero solo en la tercera declaración, cuyo valor de retorno no se usa). No puedo pensar en otro ejemplo. Habiendo sido "quemado" yo mismo, recomendaría la misma guía para otros idiomas también.

No estoy de acuerdo con la afirmación de que este exceso de rigor se debe a que muchos programadores de JS no tienen experiencia. Este es el tipo exacto de escritura típica de los programadores "demasiado inteligentes", y estoy seguro de que es mucho más común en los idiomas más tradicionales y con los desarrolladores de JS que tienen experiencia en dichos idiomas.

Privman cercano
fuente
1
Gracias por la respuesta. Necesitarás un valor de reputación de 50 o más para poder comentar. Ver: stackoverflow.com/faq
artlung
1
@artlung: Soy consciente de eso y de la razón detrás de esto. Solo decía que es molesto :)
Cerca de Privman
2

En mi experiencia, ++ i o i ++ nunca ha causado confusión más allá de la primera vez que aprendió cómo funciona el operador. Es esencial para los bucles básicos y básicos que se imparten en cualquier curso de secundaria o universidad que se imparte en idiomas en los que puede utilizar el operador. Personalmente, encuentro hacer algo como lo que se muestra a continuación para verse y leer mejor que algo con un ++ en una línea separada.

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

Al final es una preferencia de estilo y nada más, lo que es más importante es que cuando haces esto en tu código te mantienes constante para que otros que trabajan en el mismo código puedan seguir y no tener que procesar la misma funcionalidad de diferentes maneras .

Además, Crockford parece usar i- = 1, lo que me resulta más difícil de leer que --i o i--

burdz
fuente
2

Mi 2cents es que deben evitarse en dos casos:

1) Cuando tiene una variable que se usa en muchas filas y la aumenta / disminuye en la primera instrucción que la usa (o la última o, peor aún, en el medio):

// 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 ) ...

En ejemplos como este, puede pasar por alto fácilmente que la variable se incrementa / disminuye automáticamente o incluso elimina la primera instrucción. En otras palabras, úselo solo en bloques muy cortos o donde la variable aparece en el bloque en solo un par de declaraciones cercanas.

2) En caso de múltiples ++ y - aproximadamente la misma variable en la misma declaración. Es muy difícil recordar lo que sucede en casos como este:

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

Los exámenes y las pruebas profesionales preguntan sobre ejemplos como los anteriores y, de hecho, me he topado con esta pregunta mientras busco documentación sobre uno de ellos, pero en la vida real no se debe obligar a pensar tanto en una sola línea de código.

zakmck
fuente
1

¿Fortran es un lenguaje tipo C? No tiene ni ++ ni -. Así es como se escribe un bucle :

     integer i, n, sum

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

El elemento índice i es incrementado por las reglas del lenguaje cada vez a través del ciclo. Si desea aumentar en algo distinto de 1, cuente hacia atrás por dos, por ejemplo, la sintaxis es ...

      integer i

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

¿Es Python C-like? Utiliza las comprensiones de rango y lista y otras sintaxis para evitar la necesidad de incrementar un índice:

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

Entonces, en base a esta exploración rudimentaria de exactamente dos alternativas, los diseñadores de lenguaje pueden evitar ++ y, anticipando casos de uso y proporcionando una sintaxis alternativa.

¿Fortran y Python son notablemente menos imanes de errores que los lenguajes de procedimiento que tienen ++ y -? No tengo evidencia

Afirmo que Fortran y Python son similares a C porque nunca he conocido a alguien con fluidez en C que no pueda con una precisión del 90% adivinar correctamente la intención de Fortran o Python no ofuscado.

Thomas L Holaday
fuente
1
Sí, IFortran es de la década de 1950, mientras que C es de la década de 1970, así que tal vez no a que Fortran sea como C. El ejemplo de Python que carece de algo como el operador plusplus es interesante. La iteración sobre estructuras de datos se maneja de manera más simple en Python, ya que Python es JavaScript "diferente" orientado a objetos. Quizás el consejo de Crockford es usar más sintaxis de tipo OO para la iteración.
artlung