Зачем избегать приращений ( "++" ) и декрементов ( "-" ) в JavaScript?

Один из советов для инструмента jslint:

++ и -
++ (приращение) и - (декремент) операторы, как известно, вносят свой вклад в поощряя чрезмерную хитрость. Oни уступают только ошибочной архитектуре в обеспечении вирусов и других угрозы безопасности. Есть плюс вариант, запрещающий использование этих операторы.

Я знаю, что PHP-конструкции, такие как $foo[$bar++], могут легко возникать с ошибками "один за другим", но я не мог понять, как лучше управлять контуром, чем while( a < 10 ) do { /* foo */ a++; } или for (var i=0; i<10; i++) { /* foo */ }.

Является ли jslint подсветкой их, потому что есть некоторые аналогичные языки, которые не имеют синтаксиса "++" и "--" или обрабатывают его по-другому или существуют другие соображения для избежания "++" и "--", чтобы я мог отсутствовать?

Ответ 1

На мой взгляд, всегда использовать ++ и - сами по себе в одной строке, как в:

i++;
array[i] = foo;

вместо

array[++i] = foo;

Все, что за этим, может запутать некоторых программистов, и, на мой взгляд, оно того не стоит. Циклы for являются исключением, так как использование оператора приращения идиоматично и, следовательно, всегда понятно.

Ответ 2

Я откровенно смущен этим советом. Часть меня удивляется, если она имеет больше общего с отсутствием опыта (воспринимаемого или фактического) с javascript-кодонами.

Я могу видеть, как кто-то просто "взломал" в некотором примерном коде, мог совершить невинную ошибку c++ и -, но я не понимаю, почему опытный профессионал избегал бы их.

Ответ 3

В C существует история с такими вещами, как:

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

чтобы скопировать строку, возможно, это источник чрезмерного обмана, на который он ссылается.

И всегда есть вопрос о том, что

++i = i++;

или

i = i++ + ++i;

на самом деле делаю. Он определен на некоторых языках, а в других нет гарантии того, что произойдет.

В приведенных примерах я не думаю, что есть что-то более идиоматическое, чем цикл for, который использует ++ для увеличения. В некоторых случаях вы можете уйти с помощью цикла foreach или цикла while, который проверял другое состояние. Но искажение вашего кода, чтобы попытаться избежать приращения, смешно.

Ответ 4

Если вы прочитали JavaScript "Хорошие детали", вы увидите, что замена Crockford для я ++ в цикле для равна я + = 1 (не я = я + 1). Это довольно чистый и читаемый, и с меньшей вероятностью превратится во что-то "сложное".

Крокфорд запретил автоинкремент и автоопределение в jsLint. Вы выбираете, следует ли следовать совету или нет.

Мое личное правило состоит в том, чтобы не делать ничего в сочетании с автоинкрементами или автоопределением.

Я узнал из многолетнего опыта работы в C, что я не получаю переполнения буфера (или индекс массива за пределами границ), если я буду продолжать использовать его просто. Но я обнаружил, что я получаю переполнение буфера, если я попадаю в "чрезмерно сложную" практику выполнения других вещей в том же самом заявлении.

Итак, для моих собственных правил использование я ++ в качестве приращения цикла для прекрасное.

Ответ 5

В цикле это безвредно, но в операторе присваивания это может привести к неожиданным результатам:

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

Пробелы между переменной и оператором также могут привести к неожиданным результатам:

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

В заключение неожиданные результаты также могут быть проблемой:

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

И это вызывает автоматическую вставку точки с запятой после новой строки:

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

Путаница между преинкрементом и постинкрементом может привести к ошибкам, которые чрезвычайно трудно диагностировать. К счастью, они также являются полными ненужными. Есть лучшие способы добавить 1 к переменной.

Рекомендации

Ответ 6

Рассмотрим следующий код

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

так как я ++ получает оценку дважды, выход (от отладчика vs2005)

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

Теперь рассмотрим следующий код:

    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;

Обратите внимание, что вывод одинаков. Теперь вы можете подумать, что ++ я и я ++ одинаковы. Они не

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

Наконец, рассмотрим этот код

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

Теперь вывод:

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

Таким образом, они не совпадают, смешивание обоих приводит к не столь интуитивному поведению. Я думаю, что для циклов в порядке c++, но следите, когда у вас есть несколько символов ++ в одной строке или той же инструкции

Ответ 7

"pre" и "post" характер операторов приращения и декремента могут быть запутанными для тех, кто не знаком с ними; что один из способов, которым они могут быть сложными.

Ответ 8

На мой взгляд, "Явный всегда лучше, чем неявный".. В какой-то момент вас может смутить этот оператор increments y+ = x++ + ++y. Хороший программист всегда делает свой код более удобочитаемым.

Ответ 9

Я смотрел видео Douglas Crockford по этому поводу, и его объяснение невозможности использования приращений и декретов состояло в том, что

  • Он использовался в прошлом на других языках, чтобы сломать границы массивов и вызвать все манеры плохости и
  • Что более запутанные и неопытные разработчики JS не знают точно, что он делает.

Во-первых, массивы в JavaScript имеют динамический размер и поэтому, простите меня, если я ошибаюсь, невозможно разорвать границы массива и получить доступ к данным, к которым не следует обращаться с помощью этого метода в JavaScript.

Во-вторых, следует ли нам избегать сложных вещей, конечно, проблема заключается не в том, что у нас есть это средство, но проблема в том, что есть разработчики, которые утверждают, что делают JavaScript, но не знают, как работают эти операторы? Это достаточно просто. значение ++, дайте мне текущее значение и после выражения добавьте его к нему, значение ++, увеличьте значение, прежде чем дать мне это.

Выражения, такие как ++ + ++ b, просты в разработке, если вы просто помните выше.

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

Я полагаю, вы только что вспомнили, кто должен прочитать код, если у вас есть команда, которая знает JS изнутри, тогда вам не нужно беспокоиться. Если нет, то прокомментируйте это, напишите его по-другому и т.д. Сделайте то, что вам нужно сделать. Я не думаю, что прирост и декремент по своей сути являются плохими или генерируют ошибки или создают уязвимость, возможно, менее читабельны в зависимости от вашей аудитории.

Кстати, я думаю, что Дуглас Крокфорд в любом случае легенда, но я думаю, что он напугал оператора, который этого не заслужил.

Я живу, чтобы быть ошибочным, хотя...

Ответ 10

Самое важное обоснование для избежания ++ или - заключается в том, что операторы возвращают значения и вызывают побочные эффекты одновременно, что затрудняет рассуждение о коде.

Для эффективности я предпочитаю:

  • ++ i, когда не использует возвращаемое значение (без временных)
  • я ++, когда использует возвращаемое значение (без конвейера)

Я поклонник мистера Крокфорда, но в этом случае я должен не согласиться. ++i на 25% меньше текста для разбора, чем i+=1 и, возможно, более ясного.

Ответ 11

Другой пример, более простой, чем некоторые другие, с простым возвратом добавочного значения:

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

Как вы можете видеть, в return return не должно использоваться пост-инкремент/декремент, если вы хотите, чтобы этот оператор влиял на результат. Но возврат не "улавливает" операторы post-increment/decment:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}

Ответ 12

Я думаю, что программисты должны быть компетентными на языке, который они используют; используйте его четко; и использовать его хорошо. Я не думаю, что они должны искусственно калечить язык, который они используют. Я говорю по опыту. Я когда-то работал буквально по соседству с магазином Cobol, где они не использовали ELSE, потому что это было слишком сложно. Reductio ad absurdam.

Ответ 13

Я не знаю, было ли это частью его рассуждений, но если вы используете плохо написанную программу минерализации, она может превратить x++ + y в x+++y. Но опять же, плохо написанный инструмент может нанести все виды хаоса.

Ответ 14

Как уже упоминалось в некоторых из существующих ответов (которые досадно, что я не могу комментировать), проблема в том, что x ++ ++ x оценивает разные значения (до vs после инкремента), что не очевидно и может быть очень запутанно - , если используется это значение. cdmckay предлагает разумно разрешить использование оператора инкремента, но только так, чтобы возвращаемое значение не использовалось, например. по собственной линии. Я бы также включил стандартное использование в цикл for (но только в третьем операторе, возвращающее значение которого не используется). Я не могу придумать другой пример. Будучи сам "сожжен", я рекомендовал бы то же самое руководство для других языков.

Я не согласен с утверждением, что эта чрезмерность объясняется тем, что многие программисты JS неопытны. Это точный тип письма, типичный для "слишком умных" программистов, и я уверен, что он гораздо более распространен на более традиционных языках и с разработчиками JS, у которых есть фон на таких языках.

Ответ 15

По моему опыту, ++ я или я ++ никогда не вызывали путаницы, кроме того, когда вы впервые узнали о том, как работает оператор. Это важно для большинства базовых циклов и циклов, которые учат любым школьным или колледжским курсам, преподаваемым на языках, где вы можете использовать оператора. Я лично нахожу, что делаю что-то вроде того, что ниже, чтобы выглядеть и читать лучше, чем что-либо c++, находящимся в отдельной строке.

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

Ответ 16

Мои 2центы в том, что их следует избегать в двух случаях:

1) Когда у вас есть переменная, которая используется во многих строках, и вы увеличиваете/уменьшаете ее в первом утверждении, которое ее использует (или последнее или, что еще хуже, посередине):

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

В таких примерах вы можете легко пропустить, что переменная автоматически увеличивается или уменьшается или даже удаляет первый оператор. Другими словами, используйте его только в очень коротких блоках или где переменная появляется в блоке только на пару закрытых операторов.

2) В случае множественного ++ и - о той же переменной в том же самом выражении. Очень сложно запомнить, что происходит в таких случаях:

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

Экзамены и профессиональные тесты спрашивают о таких примерах, как выше, и действительно я наткнулся на этот вопрос, ища документацию об одном из них, но в реальной жизни не следует заставлять так много думать о одной строке кода.

Ответ 17

Является ли Fortran C-подобным языком? У него нет ни ++, ни.... Вот как вы пишете цикл:

     integer i, n, sum

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

Индексный элемент я каждый раз увеличивается с помощью языковых правил через цикл. Если вы хотите увеличивать значение на что-то, отличное от 1, считайте, например, два раза, синтаксис...

      integer i

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

Является ли Python C-like? Он использует методы определения диапазонов и списков и другие синтаксисы, чтобы обойти необходимость увеличения индекса:

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

Итак, основываясь на этом рудиментарном исследовании ровно двух альтернатив, разработчики языка могут избегать ++ и - предвосхищая варианты использования и предоставляя альтернативный синтаксис.

Являются ли Fortran и Python заметно меньше магнита ошибки, чем процедурные языки, которые имеют ++ и -? У меня нет доказательств.

Я утверждаю, что Fortran и Python похожи на C, потому что я никогда не встречал кого-то свободного в C, который не мог с точностью до 90% правильно угадать намерение не обфускации Fortran или Python.