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

357

Uma das dicas para a ferramenta jslint é:

++ e -
Sabe-se que os operadores ++ (incremento) e - (decremento) contribuem para códigos incorretos, incentivando truques excessivos. Eles perdem apenas para a arquitetura defeituosa na ativação de vírus e outras ameaças à segurança. Existe uma opção plusplus que proíbe o uso desses operadores.

Eu sei que construções PHP como $foo[$bar++]has podem facilmente resultar em erros de um por um, mas não consegui descobrir uma maneira melhor de controlar o loop do que um while( a < 10 ) do { /* foo */ a++; }ou for (var i=0; i<10; i++) { /* foo */ }.

O jslint está destacando-os porque existem algumas linguagens semelhantes que não possuem a sintaxe " ++" e " --" ou a tratam de maneira diferente, ou existem outras razões para evitar " ++" e " --" que eu possa estar perdendo?

artlung
fonte
59
Portanto, deve-se fazer o array [index = index + 1] em vez do array [++ index] (se o primeiro for permitido!). Que carga de hooey.
Lawrence Dol
34
Não vi Crockford index = index + 1. Eu o vi indexar + = 1. Eu acho que é uma alternativa razoável. E é bom para quando você quiser mudar o passo para algo além de 1.
Nosredna
124
Pessoalmente, eu não sou um grande fã de Crockford. Ele parece considerar qualquer coisa que já tenha causado um erro em seu código como algo maligno.
Paolo Bergantino
38
Em JavaScript, você deve considerar todo bug como algo maléfico, já que não existe documentação oficial nem fornecedor de certificados, nem você aprende JS corretamente na Universidade. Crockford e Firebug preencheram esses buracos na educação sobre JavaScript.
Stevek 19/09/10
40
++não causa bugs. O uso de ++maneiras "complicadas" pode levar a erros, especialmente se mais de uma pessoa estiver mantendo a base de código, mas isso não é um problema do operador, é um problema do programador. Eu não aprendi JS na universidade (porque ainda não existia), mas e daí? Eu fiz C, que é claro que tinha ++primeiro, mas que também recebe um "e daí?" Não fui à universidade para aprender um idioma específico, fui aprender boas práticas de programação que posso aplicar a qualquer idioma.
Nnnnnn

Respostas:

408

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 confusa para alguns programadores e simplesmente não vale a pena, na minha opinião. Os loops são uma exceção, pois o uso do operador de incremento é idiomático e, portanto, sempre claro.

cdmckay
fonte
3
Sim, é o que eu também faço. É difícil dar errado e mais fácil para outras pessoas descobrirem o que você está fazendo.
Nosredna
65
Isso parece estar chegando ao cerne da questão e à minha confusão. Usado sozinho, i ++; é claro como cristal. No minuto em que você adiciona outro código em torno dessa expressão básica, legibilidade e clareza começam a sofrer.
Artlung 9/06/09
2
Concordo, mas não é só JavaScript relacionado
Tobias
5
Você não diz se está escrevendo C ou C ++. Provavelmente, você deve usar o operador de incremento de prefixo em C ++, caso a ivariável se torne uma classe composta posteriormente; nesse caso, você criaria desnecessariamente um objeto temporário. Acho o operador postfix mais esteticamente agradável.
Mc0e
2
Eu prefiro a versão de 1 linha. Isso lembra uma operação de pilha. push é array [++ i] = foo, pop é bar = array [i--]. (Mas com matrizes baseadas em 0, a matriz [i ++] = foo e bar = matriz [- i] parece ainda melhor). Outra coisa, eu odiaria se alguém adicionasse código extra entre o i ++; e array [i] = foo ;.
Florian F
257

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

Eu posso ver como alguém que está "hackeando" algum código de exemplo pode cometer um erro inocente com ++ e -, mas não vejo por que um profissional experiente os evitaria.

Jon B
fonte
7
Bom ponto Jon B. É por isso que me incomodou tanto. Crockford é um programador de carreira (que vale décadas), familiarizado com vários idiomas ao longo de décadas e trabalha com JavaScript basicamente desde o seu início. Não acho que o conselho dele venha de "Sou um hacker preguiçoso e desatento, inexperiente" - é por isso que estou tentando entender de onde vem.
Artlung 9/06/09
14
@artlung Talvez seja mais um "hacker preguiçoso e desatento inexperiente" provavelmente mexer com esse código, e eu não quero confundi-lo.
22410 Chris Cudmore
9
Discordo completamente da sua resposta. Na minha opinião, não se trata de 'Tenho experiência suficiente para usá-lo?' - mas 'É mais claro ou não?' Se dentro de um loop for, ou sozinho, é claro - todos entenderão isso sem causar danos. O problema surge quando a ++expressão é mais complicada, na qual ++ x é diferente de x ++, resultando em algo que não é fácil de ler. A ideia de Crockford não é sobre 'posso fazer isso?' é sobre 'como posso evitar erros?'
ArtoAle
11
É a preferência pessoal em que ponto a clareza é mais importante para você do que a compacidade. Isso vai além da experiência suficiente para entender o código. Veja outras respostas para exemplos em que é mais problemático do que vale a pena.
Frug
11
Eu concordo totalmente. Por que se prejudicar por não usar parte do idioma? Encontro boas razões para usar pós e pré-incrementos e decrementos. Para mim, uma linha como essa é clara e concisa ... if (--imagesLoading === 0) start (); Se você acha que seu código não está claro, use comentários!
xtempore
69

Há uma história em C de fazer coisas como:

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

para copiar uma string, talvez essa seja a fonte do truque excessivo a que ele está se referindo.

E sempre há 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 acontecerá.

Esses exemplos à parte, acho que não há nada mais idiomático do que um loop for que usa ++para incrementar. Em alguns casos, você pode se dar bem com 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.

Eclipse
fonte
104
i = i ++ + ++ i; fez meus olhos doer um pouco - :)
Hugoware
3
Meu também. Mesmo se elas não fossem todas a mesma variável, digamos que eu tivesse x = a ++ + ++ b; - essa combinação instantaneamente torna x, aeb mais difícil de segurar na minha cabeça. agora, se cada um deles fosse declarações separadas em linhas separadas, como em - a ++; ++ b; x = a + b; seria mais fácil compreender à primeira vista.
Artlung 9/06/09
10
Infelizmente, (a ++; b ++; x = a = b) não é o mesmo valor que (a ++ + ++ b).
Thomas L Holaday
8
@Marius: x = a+++b-> x = (a++)+b-> x = a + b; a++. O tokeniser é ganancioso.
Callum Rogers
14
Para maior segurança no trabalho, remova os espaços em seu último exemplo.
Mc0e
48

Se você ler JavaScript The Good Parts, você verá que o substituto de Crockford para i ++ em um por ciclo é 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 da proibição do incremento automático e do autodecremento uma opção no jsLint. Você escolhe se segue o conselho ou não.

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

Aprendi com anos de experiência em C que não recebo saturações de buffer (ou índice de matriz fora dos limites) se eu continuar usando-o de maneira simples. Mas descobri que recebo excedentes de buffer se cair na prática "excessivamente complicada" de fazer outras coisas na mesma declaração.

Assim, por minhas próprias regras, o uso de i ++ como o incremento de uma para circuito é bom.

Nosredna
fonte
Excelente argumento sobre a opção "plusplus" ser apenas isso, uma opção. Com base nas suas e em outras respostas, acho que estou tendo a sensação de que o ++ por si só ou um loop for não são confusos por si só. No segundo em que você combina esse ++ em outra declaração, sua declaração se torna mais difícil de ler e entender no contexto.
Artlung 9/06/09
6
todo mundo recebe estouros de buffer. OpenSSH, mesmo com a sua fonte aberta e seus vários Comitês de revisão
Eric
11
@ Eric Revisitando seu comentário de cinco anos: oh, como você estava certo!
wchargin
27

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, 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 aciona 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 de pré-incremento / pós-incremento pode produzir erros pontuais que são extremamente difíceis de diagnosticar. Felizmente, eles também são completos desnecessários. Existem maneiras melhores de adicionar 1 a uma variável.

Referências

Paul Sweatte
fonte
13
Mas eles não são "inesperados" se você entende o operador. É como nunca usar ==porque você não entende a diferença entre ==e ===.
greg84
11
Exatamente. Há uma pergunta em C # que desmascara suposições falsas.
Paul Sweatte
Eu acho que o ponto de Crockford é que a maioria dos programadores não "entende completamente o operador", mesmo quando pensa que sim; portanto, há perigo em usá-los porque o potencial de mal-entendidos é alto. Veja o comentário de @ Paul sobre suposições falsas que reforça o argumento de que as pessoas geralmente entendem mal como os operadores de prefixo e postfix realmente funcionam. Dito isto, admito que gosto de usá-los, mas, para o bem dos outros, tento limitar seu uso a casos idiomáticos (veja a resposta de cdmckay ).
Gfullam 09/07/2015
Seu link de espaço em branco parece estar quebrado.
Ruslan
O que é complicado no seu exemplo de "espaço em branco"? Eu recebo uma saída direta de a: 2 b: 0 c: 1. Também não vejo nada de estranho ou inesperado no primeiro exemplo ("declaração de atribuição").
91119 Ken Williams
17

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 ++ é 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 ++ ie i ++ são iguais. Eles não são

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

Por fim, 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

Portanto, eles não são os mesmos; misturar os dois resulta em um comportamento não tão intuitivo. Eu acho que para loops estão ok com ++, mas cuidado quando você tem vários símbolos ++ na mesma linha ou na mesma instrução

Eric
fonte
3
Obrigado por fazer esse experimento. Código particularmente "simples", mas fica difícil para o meu cérebro se agarrar muito rapidamente. Definitivamente se enquadra na categoria "complicada".
Artlung 9/06/09
28
Eu parei após o primeiro exemplo. Você disse "Considere o seguinte código". Não, ninguém escreve esse código. É o idiota que escreve que é falho, não uma construção da linguagem.
21310 Sam Samwell
4
Você mostrou que dois trechos de código diferentes produzem saídas diferentes e isso prova que é ruim?
nickf
7
a conclusão é "cuidado quando você tiver vários símbolos ++ na mesma linha ou na mesma instrução". Eu não acho que você lê-lo
Eric
11
"desde que o i ++ é avaliado duas vezes" - errado! Modificar uma variável duas vezes entre os pontos de sequência e também usá-la é inválido no C ++ e leva a um comportamento indefinido. Portanto, o compilador pode fazer o que quiser. Os resultados apresentados aqui são acidentais da implementação do compilador.
tbleher
16

A natureza "pré" e "pós" dos operadores de incremento e decremento pode tender a ser confusa para aqueles que não estão familiarizados com eles; essa é uma maneira pela qual eles podem ser complicados.

Paul Sonier
fonte
29
Acordado; no entanto, um profissional experiente não deve ser confundido.
Nate
11
Eu acho que isso é semelhante à orientação para evitar tornar tudo estático ou global. Não há nada inerentemente errado com isso como uma construção de programação, mas o uso excessivo ignorante pode levar a códigos incorretos.
Joel Martinez
6
A questão não é se um bom programador poderia "seguir seu caminho" através da lógica - o bom código idealmente não requer nenhum trabalho para entender. Eu acho que o ponto de McWafflestix é que você não deve escrever código que, após uma breve inspeção, possa levar à adição de bugs em duas semanas ou dois anos. Eu sei que eu fui culpado de adicionar código para a I rotina não entendia completamente :)
Mike
11
@ Mike: sim, observe como eu não especifiquei se os operadores podem ser complicados para o autor original do código ou para os mantenedores posteriores. Geralmente, tentamos assumir que os codificadores originais não usam construções com as quais não estão familiarizados / confortáveis ​​(infelizmente, isso é frequentemente uma suposição incorreta); no entanto, essa familiaridade certamente não se estende aos mantenedores (e, como mencionado acima entre parênteses, pode nem se estender ao autor original).
9139 Paul Sonier
Tento escrever meu código para outros programadores lerem, mas não tento escrever para iniciantes.
Luke H
16

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

Sriram
fonte
11
Não há nada implícito em x ++ ou ++ y.
Florian F
11
Esse código legível é complicado ... Eu acho que existe uma linha de base para a simplicidade do seu código, e nós - como comunidade - precisamos prosperar um pouco e ser capazes de ler o que agora não é legível, como one-liners com ternários aninhadas, como para permitir que mais coisas para chutar.
Hamza Ouaghad
14

A justificativa 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.

Por uma questão de eficiência, prefiro:

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

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

Hans Malherbe
fonte
13

Estive assistindo o vídeo de Douglas Crockford sobre isso e sua explicação para não usar incremento e decremento é que

  1. Foi usado no passado em outros idiomas para quebrar os limites de matrizes e causar todas as maneiras de maldade e
  2. Por ser mais confuso e inexperiente, os desenvolvedores de JS não sabem exatamente o que fazem.

Em primeiro lugar, as matrizes em JavaScript são dinamicamente dimensionadas e, portanto, desculpe-me se 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 complicadas, certamente o problema não é que temos esse recurso, mas o problema é que existem desenvolvedores por aí que afirmam fazer JavaScript, mas não sabem como esses operadores funcionam? É bastante simples. value ++, me dê o valor atual e, após a expressão, adicione um, ++ value, aumente o valor antes de me fornecer.

Expressões como a ++ + ++ 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;

Suponho que você apenas se lembre de quem precisa ler o código. Se você tem uma equipe que conhece JS de dentro para fora, não precisa se preocupar. Caso contrário, comente-o, escreva-o de forma diferente, etc. Faça o que você deve fazer. Não acho que incremento e decremento sejam inerentemente ruins ou gerem bugs ou criem vulnerabilidades, talvez apenas menos legíveis, dependendo do seu público.

Aliás, acho que Douglas Crockford é uma lenda de qualquer maneira, mas acho que ele causou muito susto em um operador que não o merecia.

Eu vivo para ser provado errado embora ...

Stuart Wakefield
fonte
10

Outro exemplo, mais simples que 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ê desejar que este operador influencie o resultado. Mas o retorno não "captura" os operadores pós-incremento / decremento:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}
Evgeny Levin
fonte
Apenas uma das suas funções recebe x como parâmetro #
Calimero100582 15/10
8

Eu acho que os programadores devem ser competentes no idioma que estão usando; use-o claramente; e use-o bem. Eu não acho que eles devam aleijar artificialmente o idioma que estão usando. Eu falo por experiência própria. Certa vez, eu trabalhei literalmente ao lado de uma loja Cobol onde eles não usavam o ELSE 'porque era muito complicado'. Reductio ad absurdam.

Marquês de Lorne
fonte
@downvoter Fascinante. Você não acha que os programadores devem ser competentes no idioma? Você acha que eles deveriam aleijar artificialmente o idioma? Qual é? Não há terceira opção aqui.
Marquês de Lorne
11
e um upvote para esse exemplo COBOL, me deixa ainda mais feliz que COBOL principalmente morreu antes de eu entrar codificação
Mark K Cowan
11
@ MarkCowan Não vejo o porquê. Meu argumento é sobre a restrição arbitrária, não a linguagem. A anedota não indica nada de errado com Cobol. O que precisava desaparecer era aquela loja de programação, e funcionou. Visite as entranhas de qualquer grande banco e você verá que Cobol está vivo e bem.
Marquês de Lorne
2

Como mencionado em algumas das respostas existentes (que irritantemente não consigo comentar), o problema é que x ++ ++ x é avaliado com 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 com muita sabedoria 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. Também incluiria o uso padrão em 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.

Não concordo com a afirmação de que esse excesso de rigidez deve-se ao fato de muitos programadores de 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 em tais linguagens.

Perto de Privman
fonte
11
Obrigado pela resposta. Você precisará de um valor de reputação igual ou superior a 50 para comentar. Veja: stackoverflow.com/faq
artlung
11
@artlung: Estou ciente disso e da lógica por trás disso. Eu estava apenas dizendo que é chato :)
Perto Privman
2

Na minha experiência, ++ i ou i ++ nunca causou confusão, a não ser quando aprendemos pela primeira vez sobre como o operador funciona. É essencial para os mais básicos loops e loops ministrados por qualquer curso de ensino médio ou superior, ministrado em idiomas nos quais você pode usar o operador. Pessoalmente, acho que fazer algo como o que está abaixo para parecer e ler melhor do que algo com um ++ estando em uma linha separada.

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

No final, é uma preferência de estilo e nada mais, o mais importante é que, quando você faz isso em seu código, permanece consistente para que outras pessoas que trabalham no mesmo código possam seguir e não precisar processar a mesma funcionalidade de maneiras diferentes. .

Além disso, Crockford parece usar i- = 1, que acho mais difícil de ler do que --i ou i--

burdz
fonte
2

Meu 2 centavos é que eles devem ser evitados em dois casos:

1) Quando você tem uma variável usada em várias linhas e 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 esquecer que a variável é incrementada / decrementada automaticamente ou até remover a primeira instrução. Em outras palavras, use-o apenas em blocos muito curtos ou em que a variável apareça no bloco em apenas algumas instruções próximas.

2) No caso de múltiplos ++ e - sobre a mesma variável na mesma instruçã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 me deparei com essa pergunta enquanto procurava documentação sobre um deles, mas na vida real não devemos ser forçados a pensar tanto em uma única linha de código.

zakmck
fonte
1

O Fortran é um idioma semelhante ao C? Não possui 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 linguagem todas as vezes no loop. Se você deseja incrementar algo diferente de 1, conte duas vezes para trás, por exemplo, a sintaxe é ...

      integer i

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

O Python é do tipo C? Ele usa compreensões de intervalo e lista e outras sintaxes para ignorar 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 casos de uso e fornecendo uma sintaxe alternativa.

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

Afirmo que Fortran e Python são do tipo C porque nunca conheci alguém fluente em C que não pudesse, com precisão de 90%, adivinhar corretamente a intenção do Fortran ou do Python não ofuscados.

Thomas L Holaday
fonte
11
Sim, IFortran é da década de 1950, enquanto C é da década de 1970, então talvez não seja para Fortran ser do tipo C. O exemplo de Python sem algo como o operador plusplus é interessante. A iteração sobre estruturas de dados é mais claramente tratada no Python, sendo o Python "diferentemente" JavaScript orientado a objetos. Talvez o conselho de Crockford seja sobre o uso de uma sintaxe semelhante ao OO para iteração.
Artlung 9/06/09