Por que evitar operadores de incremento (“++”) e decremento (“-”) em JavaScript?

Uma das dicas para a ferramenta jslint é:

++ e –
Sabe-se que os operadores ++ (incremento) e – (decremento) contribuem para o código incorreto, incentivando o excesso de truques. Eles perdem apenas para a arquitetura defeituosa ao permitir vírus e outras ameaças de segurança. Existe uma opção plusplus que proíbe o uso desses operadores.

Eu sei que o PHP constrói como $foo[$bar++] pode facilmente resultar com erros off-by-one, mas eu não consegui descobrir uma maneira melhor de controlar o loop do que um while( a < 10 ) do { /* foo */ a++; } while( a < 10 ) do { /* foo */ a++; } ou for (var i=0; i<10; i++) { /* foo */ } .

O jslint está destacando-os porque existem algumas linguagens similares que não possuem a syntax++ ” e ” -- ” ou o manipulam de forma diferente, ou existem outras razões para evitar ” ++ ” e ” -- ” que eu possa estar faltando? ?

Minha opinião é sempre usar ++ e – sozinhos em uma única linha, como em:

 i++; array[i] = foo; 

ao invés de

 array[++i] = foo; 

Qualquer coisa além disso pode ser confuso para alguns programadores e simplesmente não vale a pena, na minha opinião. For loops são uma exceção, já que o uso do operador de incremento é idiomático e, portanto, sempre claro.

Estou francamente confuso com esse conselho. Parte de mim se pergunta se tem mais a ver com a falta de experiência (percebida ou real) com os codificadores de javascript.

Eu posso ver como alguém apenas “hacking” em algum código de exemplo poderia cometer um erro inocente com ++ e -, mas não vejo por que um profissional experiente iria evitá-los.

Existe uma história no C de fazer coisas como:

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

para copiar uma string, talvez esta seja a fonte do excesso de truques a que ele está se referindo.

E há sempre a questão do que

 ++i = i++; 

ou

 i = i++ + ++i; 

realmente fazer. É definido em alguns idiomas e em outros não há garantia do que vai acontecer.

Deixando de lado esses exemplos, não acho que exista algo mais idiomático do que um loop for que use ++ para incrementar. Em alguns casos, você pode obter um loop foreach ou um loop while que verifica uma condição diferente. Mas contorcer seu código para tentar evitar o incremento é ridículo.

Se você ler JavaScript The Good Parts, verá que o substituto de Crockford para o i ++ em um loop for é i + = 1 (não i = i + 1). Isso é bastante limpo e legível, e é menos provável que se transforme em algo “complicado”.

Crockford fez desautorização autoincrement e autodecrement uma opção em jsLint. Você escolhe se quer seguir o conselho ou não.

Minha regra pessoal é não fazer nada combinado com incremento automático ou autodecrement.

Eu aprendi com anos de experiência em C que eu não recebo estouro de buffer (ou índice de array fora dos limites) se eu mantenho o uso simples. Mas eu descobri que recebo estouro de buffer se eu cair na prática “excessivamente complicada” de fazer outras coisas na mesma declaração.

Então, para minhas próprias regras, o uso de i ++ como incremento em um loop for está bem.

Em um loop é inofensivo, mas em uma declaração de atribuição pode levar 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 

O espaço em branco entre a variável e o operador também pode levar a resultados inesperados:

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

Em um fechamento, os resultados inesperados também podem ser um 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 

E isso desencadeia a inserção automática de ponto-e-vírgula após uma nova linha:

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

A confusão pré-incremento / pós-incremento pode produzir erros extremos que são extremamente difíceis de diagnosticar. Felizmente, eles também são completamente desnecessários. Existem maneiras melhores de adicionar 1 a uma variável.

Referências

  • JSLint Help: Operadores de Incremento e Decremento

Considere o seguinte 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++; 

desde que o i ++ seja avaliado duas vezes a saída é (do depurador vs2005)

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

Agora considere o seguinte 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; 

Observe que a saída é a mesma. Agora você pode pensar que ++ i e i ++ são os mesmos. Eles não são

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

A saída é agora:

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

Então, eles não são os mesmos, misturando ambos resultam em um comportamento não tão intuitivo. Eu acho que for loops são ok com ++, mas cuidado quando você tem vários símbolos ++ na mesma linha ou mesmo instrução

A natureza “pré” e “pós” dos operadores de incremento e decremento tendem a ser confusos para aqueles que não estão familiarizados com eles; Essa é uma maneira em que eles podem ser complicados.

Na minha opinião, “explícito é sempre melhor do que implícito”. Porque em algum momento, você pode ficar confuso com essa declaração de incrementos y+ = x++ + ++y . Um bom programador sempre torna seu código mais legível.

Eu tenho assistido o vídeo de Douglas Crockford sobre isso e sua explicação para não usar incremento e decréscimo é que

  1. Ele foi usado no passado em outras línguas para quebrar os limites das matrizes e causar todas as maneiras de maldade e
  2. Que é mais confuso e inexperiente desenvolvedores JS não sabem exatamente o que faz.

Em primeiro lugar arrays em JavaScript são dimensionados dinamicamente e assim, me perdoe se eu estiver errado, não é possível quebrar os limites de uma matriz e acessar dados que não devem ser acessados ​​usando esse método em JavaScript.

Em segundo lugar, devemos evitar coisas que são complicadas, certamente o problema não é que temos esse recurso, mas o problema é que existem desenvolvedores por aí que pretendem fazer JavaScript, mas não sabem como esses operadores funcionam? É bem simples. valor ++, me dá o valor atual e após a expressão adicionar um a ele, valor ++, incrementa o valor antes de me dar.

Expressões como ++ + ++ b, são simples de resolver se você se lembrar do que foi dito acima.

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

Eu suponho que você só tem que lembrar quem tem que ler o código, se você tem uma equipe que conhece JS de dentro para fora, então você não precisa se preocupar. Se não, então comente, escreva de forma diferente, etc. Faça o que tiver que fazer. Eu não acho que incremento e decremento são inerentemente ruins ou geradores de bugs, ou criação de vulnerabilidades, talvez apenas menos legíveis dependendo do seu público.

A propósito, acho que Douglas Crockford é uma lenda de qualquer maneira, mas acho que ele causou muito medo a um operador que não merecia isso.

Eu vivo para ser provado errado embora …

A razão mais importante para evitar ++ ou – é que os operadores retornem valores e causem efeitos colaterais ao mesmo tempo, dificultando o raciocínio sobre o código.

Pelo bem da eficiência, eu prefiro:

  • ++ i quando não estiver usando o valor de retorno (não temporário)
  • i ++ ao usar o valor de retorno (sem parada no pipeline)

Eu sou fã do Sr. Crockford, mas neste caso eu tenho que discordar. ++i é 25% menos texto para analisar que i+=1 e sem dúvida mais clara.

Outro exemplo, mais simples que alguns outros com retorno simples 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 você pode ver, nenhum pós-incremento / decremento deve ser usado na declaração de retorno, se você quiser que este operador influencie o resultado. Mas o retorno não “pega” operadores pós-incremento / decremento:

 function closureIncrementTest() { var x = 0; function postIncrementX() { return x++; } var y = postIncrementX(); console.log(x); // 1 } 

Eu acho que os programadores devem ser competentes na linguagem que estão usando; use-o claramente; e usá-lo bem. Eu não acho que eles devam aleijar artificialmente a linguagem que estão usando. Eu falo por experiência. Eu trabalhei literalmente uma vez ao lado de uma loja de Cobol onde eles não usaram ELSE ‘porque era muito complicado.’ Reductio ad absurdam.

Não sei se isso fazia parte do seu raciocínio, mas se você usa um programa de minificação mal escrito, ele pode transformar x++ + y em x+++y . Mas, novamente, uma ferramenta mal escrita pode causar todos os tipos de estragos.

Como mencionado em algumas das respostas existentes (o que é irritantemente incapaz de comentar), o problema é que x ++ ++ x avaliam valores diferentes (antes vs após o incremento), o que não é óbvio e pode ser muito confuso – se esse valor for usado. O cdmckay sugere muito sabiamente para permitir o uso do operador de incremento, mas apenas de uma maneira que o valor retornado não seja usado, por exemplo, em sua própria linha. Eu também includeia o uso padrão dentro de um loop for (mas apenas na terceira instrução, cujo valor de retorno não é usado). Não consigo pensar em outro exemplo. Tendo sido “queimado”, eu recomendaria a mesma diretriz para outros idiomas também.

Eu não concordo com a alegação de que esse excesso de rigidez é devido a muitos programadores JS serem inexperientes. Esse é o tipo exato de escrita típico de programadores “excessivamente inteligentes”, e tenho certeza de que é muito mais comum em linguagens mais tradicionais e com desenvolvedores de JS que têm experiência nessas linguagens.

Na minha experiência, ++ i ou i ++ nunca causou confusão a não ser quando aprendemos sobre como o operador funciona. É essencial para os loops e loops mais básicos ensinados por qualquer curso de ensino médio ou universitário ministrado em idiomas onde você possa usar o operador. Eu pessoalmente acho que fazer algo parecido com o que está abaixo para olhar e ler melhor do que algo com um ++ estar em uma linha separada.

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

Meus 2cents é que eles devem ser evitados em dois casos:

1) Quando você tem uma variável que é usada em muitas linhas e você a aumenta / diminui na primeira instrução que a usa (ou por último, ou, pior ainda, no meio):

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

Em exemplos como esse, você pode facilmente perder que a variável é incrementada / decrementada automaticamente ou até mesmo remover a primeira instrução. Em outras palavras, use-a apenas em blocos muito curtos ou onde a variável apareça no bloco em apenas algumas instruções de fechamento.

2) No caso de múltiplos ++ e - sobre a mesma variável na mesma declaração. É muito difícil lembrar o que acontece em casos como este:

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

Exames e testes profissionais perguntam sobre exemplos como acima e de fato eu tropecei nesta questão enquanto procurava por documentação sobre um deles, mas na vida real não se deve ser forçado a pensar tanto sobre uma única linha de código.

Fortran é uma linguagem semelhante ao C? Não tem nem ++ nem -. Aqui está como você escreve um loop :

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

O elemento de índice i é incrementado pelas regras de idioma a cada vez através do loop. Se você quiser incrementar por algo diferente de 1, conte de volta por dois, por exemplo, a syntax é …

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

O Python é semelhante ao C? Ele usa abrangência e compreensão de lista e outras syntaxs para contornar a necessidade de incrementar um í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 ... ] 

Portanto, com base nessa exploração rudimentar de exatamente duas alternativas, os designers de linguagem podem evitar ++ e – antecipando os casos de uso e fornecendo uma syntax alternativa.

O Fortran e o Python são notavelmente menos imunes do que linguagens procedurais que possuem ++ e -? Eu não tenho provas.

Eu afirmo que Fortran e Python são semelhantes a C porque eu nunca conheci alguém fluente em C que não pudesse com 90% de precisão adivinhar corretamente a intenção do Fortran não-ofuscado ou Python.