Зачем избегать операций увеличения ("++") и уменьшения ("-") в JavaScript?

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

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

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

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

20 ответов

Решение

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

i++;
array[i] = foo;

вместо

array[++i] = foo;

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

Я откровенно смущен этим советом. Часть меня интересует, связано ли это с отсутствием опыта (воспринимаемого или действительного) с кодировщиками JavaScript.

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

В С есть история таких вещей, как:

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

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

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

++i = i++;

или же

i = i++ + ++i;

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

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

Если вы прочитаете JavaScript The Good Parts, вы увидите, что замена Крокфорда для i++ в цикле for - это i+=1 (не i=i+1). Это довольно чисто и читабельно, и с меньшей вероятностью превратится во что-то "хитрое".

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

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

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

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

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

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 к переменной.

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

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

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

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

так как 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;

Обратите внимание, что вывод одинаков. Теперь вы можете подумать, что ++ 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

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

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

Я смотрел видео Дугласа Крокфорда об этом, и его объяснение того, что не используется приращение и уменьшение, состоит в том, что

  1. Это использовалось в прошлом на других языках, чтобы выйти за границы массивов и вызвать все манеры зла и
  2. Что это больше сбивает с толку и неопытные разработчики 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 наизнанку, вам не нужно беспокоиться. Если нет, то прокомментируйте, напишите по-другому и т. Д. Делайте то, что вы должны делать. Я не думаю, что инкремент и декремент по своей сути плох, генерируют ошибки или создают уязвимости, возможно, просто менее читабельны в зависимости от вашей аудитории.

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

Я живу, чтобы оказаться неправым, хотя...

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

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

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

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

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

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 не "ловит" операторы постинкрементного увеличения / уменьшения:

function closureIncrementTest() {
    var x = 0;

    function postIncrementX() {
        return x++;
    }

    var y = postIncrementX();

    console.log(x); // 1
}

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

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

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

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

Кроме того, Крокфорд, кажется, использует i-=1, что, как мне кажется, труднее читать, чем --i или i--

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

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

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

Мой 2cents заключается в том, что их следует избегать в двух случаях:

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

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

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

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

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

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

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

     integer i, n, sum

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

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

      integer i

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

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

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

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

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

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

Операторы означают разные вещи, когда используются в качестве префиксов по сравнению с суффиксами, что может вызвать труднодоступные ошибки. Рассмотрим следующий пример с помощью bubbleSort:

      function bubbleSort(array) {
  if(array.length === 1) return array;

  let end = array.length - 2;
  do {
    for (let i = 0; i < array.length; i += 1) {
      if (array[i] > array[i + 1]) {
        swap(array, i, i + 1);
      }
    }
  } while (end--);
}

bubbleSort([6,5]);

Представим, что в ходе выполнения нашей программы мы передаем значение из двух элементов в нашу функцию сортировки. Код работает нормально как есть: цикл «do / while» сначала выполняется до достижения условия. Однако программа видит, что endявляется ложным и выходит из цикла до уменьшения переменной.

Теперь рассмотрим следующий код, где --символ используется как префикс, а не как суффикс. Этот код войдет в бесконечный цикл:

      function bubbleSort(array) {
  if(array.length === 1) return array;

  let end = array.length - 2;
  do {
    for (let i = 0; i < array.length; i += 1) {
      if (array[i] > array[i + 1]) {
        swap(array, i, i + 1);
      }
    }
  } while (--end);
}

bubbleSort([6,5]);

Теперь, когда мы достигаем условия while, мы уменьшаем конечное значение перед его проверкой. Это возвращает -1, что в Javascript является истинным значением.

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

это для хардкорных геймеров, которые не хотят тратить время на цикл for в setinterval

      var current = []; 
var currentb = []; 
current[0] = 0; 
currentb[0] = 0; 
setInterval(function(){ 
current[0] = current[0] + 1; 
currentb[0] = currentb[0] + 1; 
if(current[0] > array.length){ 
current[0] = 0; 
}else { 
var div = document.createElement("div"); 
div.style.width = array[current] + "px"; 
div.style.height = array[current] + "px"; 
div.style.background = "red"; 
div.style.color = "white"; div.innerHTML = "Hello";
document.getElementById("id").appendChild(div); } 
if(currentb[0] > arrayb.length){ 
currentb[0] = 0; 
} else { 
var div = document.createElement("div"); 
div.style.width = arrayb[currentb] + "px"; 
div.style.height = arrayb[currentb] + "px"; 
div.style.background = "red"; 
div.style.color = "white"; div.innerHTML = "Hello";
document.getElementById("id").appendChild(div); }, 3000);

Аргумент в пользу ++ заключается в том, что ++ не применим к строкам, которые вызовут TypeError, что почти всегда предпочтительнее.

Другие вопросы по тегам