JavaScriptでインクリメント(「++」)およびデクリメント(「-」)演算子を使用しないのはなぜですか?


357

jslintツールヒントの 1つは次のとおりです

++および-
++(インクリメント)および-(デクリメント)演算子は、過度のトリッキーを助長することにより、不正なコードに寄与することが知られています。これらは、ウイルスやその他のセキュリティ上の脅威を可能にする点で、欠陥のあるアーキテクチャに次ぐ第2位です。これらの演算子の使用を禁止するplusplusオプションがあります。

のようなPHPの構成要素$foo[$bar++]が1つずれるエラーで簡単に発生する可能性があることを知っていますが、ループを制御するためのより良い方法をa while( a < 10 ) do { /* foo */ a++; }やよりも理解できませんでしたfor (var i=0; i<10; i++) { /* foo */ }

「欠けているいくつかの類似した言語があるので、それらを強調しjslintである++」と「--」構文または異なって、それを処理し、または回避するため、他の理論的根拠があるが、「++」と「--私は失われるかもしれませんか」?


59
したがって、array [++ index]ではなくarray [index = index + 1]を実行する必要があります(前者も許可されている場合)。なんという負荷だ。
ローレンスドル

34
Crockfordがindex = index + 1を実行するのを見たことはありません。私は彼がindex + = 1をするのを見ました。それは合理的な選択肢だと思います。そして、ストライドを1以外の何かに変更したい場合に
最適

124
個人的に私はクロックフォードの大ファンではありません。彼はコードにバグを引き起こしたことのあるものはすべて悪だと考えているようです。
Paolo Bergantino、

38
JavaScriptでは、公式のドキュメントも証明書プロバイダーも存在せず、大学でJSを適切に学習していないため、すべてのバグをいくぶん悪いと考えるべきです。CrockfordとFirebugはJavaScript教育におけるこれらの穴を埋めました。
stevek

40
++バグは発生しません。++「トリッキー」な方法で使用すると、特に複数の人がコードベースを保守している場合にバグが発生する可能性があります、それはオペレーターの問題ではなく、プログラマーの問題です。私はJSを大学で学びませんでした(まだ存在しなかったため)が、どうですか?もちろん++最初に行ったCを実行しましたが、それでも「何が原因ですか?」私は特定の言語を学ぶために大学に行ったのではなく、どの言語にも適用できる優れたプログラミング実践を学びに行きました。
nnnnnn

回答:


408

私の見解は、常に++と-を使用することです。

i++;
array[i] = foo;

の代わりに

array[++i] = foo;

それを超えるものはプログラマーを混乱させる可能性があり、私の見解ではそれだけの価値はありません。forループは例外です。インクリメント演算子の使用は慣用的であり、常に明確であるためです。


3
ええ、それも私がやっていることです。間違いを犯しにくく、他の人があなたのしていることを理解しやすくなります。
Nosredna

65
それが問題と私の混乱の核心になっているようです。単独で使用、i ++; クリスタルクリアです。基本的な表現の周りに他のコードを追加すると、読みやすさと明瞭さが損なわれ始めます。
artlung 2009年

2
同意しますが、それはJavaScript関連だけではありません
Tobias

5
CとC ++のどちらを作成しているかは言いません。おそらくi変数が後で複合クラスになる場合に備えて、C ++では前置インクリメント演算子を使用する必要があります。その場合、不必要に一時オブジェクトを作成することになります。Postfix演算子の方が見た目が美しく感じられます。
mc0e 14年

2
私は1行バージョンを好む。スタック操作を思い出させます。pushはarray [++ i] = foo、popはbar = array [i--]です。(ただし、0ベースの配列では、array [i ++] = fooおよびbar = array [-i]の方が見栄えがよくなります)。もう1つ、誰かがi ++の間に追加のコードを追加した場合、私はそれを嫌います。そして、array [i] = foo;。
フロリアンF

257

私はそのアドバイスに率直に混乱しています。私の一部は、それがjavascriptコーダーの(知覚されたまたは実際の)経験の欠如と関係があるのではないかと思っています。

一部のサンプルコードを "ハッキング"するだけで、++と-で無実の間違いを犯す可能性があることはわかりますが、経験豊富な専門家がそれらを回避する理由はわかりません。


7
良い点ジョンB.だから私はとても困っています。Crockfordは、何十年にもわたって複数の言語に精通したキャリアプログラマー(数十年に相当)であり、JavaScriptの創業以来、基本的にJavaScriptを使用しています。彼のアドバイスは「私は怠惰で無関心で経験の浅いハッカー」から来ているとは思いません-それが私がそれがどこから来ているのかを理解しようとしている理由です。
artlung 2009年

14
@artlung多分、「怠惰で不注意な、経験の浅いハッカー」との関係は、おそらくこのコードを台無しにするでしょう、そして私は彼を混乱させたくないでしょう。
Chris Cudmore

9
私はあなたの答えに完全に同意しません。私の意見では、「私はそれを使うのに十分な経験がありますか?」-しかし、「明確かどうか」forループ内または単独の場合、それは非常に明確です-誰もが害を与えることなくそれを理解します。++++ xがx ++と異なるため、より複雑な式を挿入すると問題が発生し、その結果、読みにくいものになります。クロックフォードのアイデアは、「私にできるか」ではありません。それは「どうすればエラーを回避できるか」についてです。
ArtoAle 2012

1
コンパクトさよりも明確さの方が重要なのは、個人的な好みです。これは、コードを理解するのに十分な経験があるだけではありません。価値よりも問題が多い例については、他の回答を参照してください。
Frug

1
全くもって同じ意見です。言語の一部を使用しないことによってなぜ自分自身をハンディキャップにするのですか?ポストインクリメントとプレインクリメントの両方を使用することには十分な理由があります。私には、このような行は明確で簡潔です... if(--imagesLoading === 0)start(); コードが不明確だと思われる場合は、コメントを使用してください!
xtempore

69

Cには次のようなことをした歴史があります。

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

文字列をコピーするために、おそらくこれが彼が言及している過剰な策略の原因です。

そして、常に何の問題があります

++i = i++;

または

i = i++ + ++i;

実際に。これは一部の言語で定義されており、他の言語では何が起こるかを保証するものではありません。

これらの例は別として、++インクリメントに使用するforループ以外に慣用的なものはないと思います。場合によっては、foreachループや、別の条件をチェックするwhileループを回避することができます。しかし、インクリメントを使用しないようにコードを変形するのはばかげています。


104
i = i ++ + ++ i; 私の目が少し傷ついた-:)
Hugoware

3
あまりにも私のもの。それらがすべて同じ変数でなかったとしても、x = a ++ + ++ bがあったとしましょう。-この組み合わせにより、x、a、およびbがすぐに頭の中に収まりにくくなります。-a ++;のように、それぞれが別々の行の別々のステートメントである場合 ++ b; x = a + b; 一目で理解しやすいでしょう。
artlung 2009年

10
残念ながら、(a ++; b ++; x = a = b)は(a ++ + ++ b)と同じ値ではありません。
Thomas L Holaday、

8
@マリウス:x = a+++b-> x = (a++)+b-> x = a + b; a++。トークナイザーは貪欲です。
Callum Rogers

14
作業の安全性を高めるために、最後の例ではスペースを削除してください。
mc0e 2014年

48

JavaScript The Good Partsを読むと、Crockfordによるforループでのi ++の置き換えはi + = 1(i = i + 1ではない)であることがわかります。それはかなりクリーンで読みやすく、「トリッキーな」何かに変形する可能性は低くなります。

Crockfordは、自動インクリメントと自動デクリメントの禁止をjsLintのオプションにしました。アドバイスに従うかどうかを選択します。

私の個人的なルールは、自動インクリメントまたは自動デクリメントと組み合わせて何もしないことです。

Cでの長年の経験から、単純に使用し続けてもバッファオーバーラン(または範囲外の配列インデックス)が発生しないことを学びました。しかし、同じステートメントで他のことを実行する「過度にトリッキーな」習慣に陥ると、バッファオーバーランが発生することを発見しました。

したがって、私自身のルールでは、forループの増分としてi ++を使用することは問題ありません。


「plusplus」オプションがそれだけのオプションであることの優れた点。あなたと他の回答に基づいて、++自体またはforループ自体が混乱しないという感覚を得ていると思います。2番目にその++を別のステートメントに結合する2番目のステートメントは、rステートメントが読みやすく、コンテキスト内で理解するのがより困難になります。
artlung 2009年

6
誰もがバッファオーバーフローを起こします。OpenSSHとオープンソースおよび複数の改訂委員会
Eric

11
@Eric 5年前のコメントを再訪する:おお、あなたは正しかった!
wchargin 2015

27

ループでは無害ですが、代入ステートメントでは予期しない結果になる可能性があります。

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つずれるエラーが発生する可能性があります。幸いにも、それらは完全に不要です。変数に1を追加するより良い方法があります。

参考文献


13
しかし、演算子を理解していれば、それらは「予期しない」ものではありません。それは今まで使用していないようなものだ==あなたは違いを理解していないため、=====
greg84 2013年

1
丁度。ありますC#の質問誤った前提誤りを暴くビデオ。
Paul Sweatte 2013年

Crockfordの要点は、ほとんどのプログラマーは、彼らが理解していると思っても、完全に「オペレーターを理解する」ことはできないということだと思います。したがって、誤解の可能性が高いため、それらを使用することには危険があります。人々が一般に前置演算子と後置演算子が実際に機能する方法を誤解しているという主張を強化する誤った仮定についての@Paulのコメントを参照してください。そうは言っても、私はそれらを使用するのが好きだと認めていますが、他の人のために、それらの使用を慣用的なケースに限定しようとしています(cdmckayの回答を参照)。
gfullam、2015

ホワイトスペースのリンクが壊れているようです。
ルスラン

あなたの「空白」の例についてトリッキーなことは何ですか?の簡単な出力が得られますa: 2 b: 0 c: 1。最初の例(「割り当てステートメント」)にも奇妙なものや予期しないものはありません。
ケンウィリアムズ

17

次のコードを検討してください

    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 ++は2回評価されるため、出力は(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ループは++では問題ないと思いますが、同じ行または同じ命令に複数の++シンボルがある場合は注意してください


3
その実験をしてくれてありがとう。特に「単純に見える」コードですが、脳がすぐに把握するのが難しくなります。間違いなく「トリッキー」カテゴリに分類されます。
2009年

28
最初の例の後、私は立ち止まりました。「次のコードを検討してください」と言いました。いいえ、そのコードは誰も作成していません。言語の構成ではなく、欠陥のあるものを書いているのはばかです。
サムハーウェル

4
2つの異なるコードが異なる出力を生成することを示しましたが、これはそれが悪いことを証明していますか?
nickf 2009

7
結論は、「同じ行または同じ命令に複数の++シンボルがある場合に注意してください」です。私はあなたがそれを読んだとは思いません
エリック

1
「i ++は2回評価されるため」-間違っています。シーケンスポイント間で変数を2回変更し、それを使用すると、C ++では無効になり、動作が未定義になります。そのため、コンパイラーは必要な処理をすべて実行できます。ここに示されている結果は、コンパイラー実装の偶発的なものです。
2013年

16

インクリメント演算子とデクリメント演算子の「前」と「後」の性質は、慣れていない人を混乱させる傾向があります。これが、トリッキーになる可能性がある1つの方法です。


29
同意した ただし、経験豊富な専門家混乱させるべきではありません。
ネイト、

1
これは、すべてを静的またはグローバルにすることを避けるためのガイダンスに似ていると思います。プログラミング構造としての本質的な問題は何もありませんが、無知な使い過ぎは悪いコードにつながる可能性があります。
Joel Martinez

6
問題は、優れたプログラマーがロジックを通して「自分のやり方で」作業できるかどうかではありません。理想的には、優れたコードは理解するための作業を必要としません。McWafflestixのポイントは、簡単な検査で2週間または2年間でバグが追加される可能性があるコードを記述してはならないということです。私は完全に理解できなかったルーチンにコードを追加したことで罪を犯したことを知っています:)
Mike

1
@マイク:ええ、演算子がコードの元の作者や後のメンテナにとってトリッキーであるかどうかを指定しなかったことに注意してください。一般に、元のコーダーは慣れていない/使い慣れていない構成を使用しないと想定します(残念ながら、これは多くの場合、誤った想定です)。しかし、その親しみやすさは確かにメンテナーにまでは及ばない(そして、上記の括弧で述べたように、元の作者にも及ばないかもしれない)。
ポール・ソニア

私は他のプログラマーが読めるように自分のコードを書こうとしますが、初心者が読めるように書こうとはしません
ルークH

16

私の見解では、「明示的は常に暗黙的よりも優れています」。ある時点で、このインクリメントステートメントと混同されることがありますy+ = x++ + ++y。優れたプログラマは常に自分のコードを読みやすくします。


1
x ++または++ yには暗黙的なものはありません。
フロリアンF

1
この読みやすいコードはトリッキーです...私はあなたのコードがいかに単純でなければならないかについてのベースラインがあると思います、そして私たちは-コミュニティとして-少し成長し、今では読み込めないものを読むことができなければなりません(ワンライナーなど)でキックに多くの事を可能にするため、ネストされた三元系で。
ハムザOuaghad

14

++または-を回避するための最も重要な根拠は、演算子が値を返すと同時に副作用を引き起こすため、コードについて推論することが困難になることです。

効率のために、私は次のことを好みます。

  • 戻り値を使用ない場合の++ i(一時的ではない)
  • 戻り値を使用する場合のi ++(パイプラインストールなし)

私はクロックフォード氏のファンですが、この場合は反対しなければなりません。++i解析するテキストが25%少なくi+=1 間違いなく明確です。


13

私はこれに関するダグラス・クロックフォードのビデオを見てきました、そしてインクリメントとデクリメントを使わないという彼の説明はそれです

  1. 過去に他の言語で使用されて配列の境界を破り、あらゆる種類の悪さを引き起こし、
  2. それがより混乱していて、経験の浅いJS開発者は、それが何をするのか正確に知りません。

まず、JavaScriptの配列は動的にサイズ変更されるため、私が間違っていても、配列の境界を破って、JavaScriptでこのメソッドを使用してアクセスすべきでないデータにアクセスすることはできません。

第二に、複雑なものを避けるべきです。確かに問題はこの機能があることではありませんが、JavaScriptを実行すると主張しているが、これらの演算子がどのように機能するかわからない開発者がいるということです。とてもシンプルです。value ++、現在の値を与え、式の後に++ valueを加えてから、値を増やしてから与えてください。

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

インクリメントされた値を返すだけで、他の例よりも簡単な別の例:

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
}

関数の1つだけがxをパラメーターとして受け取ります
Calimero100582

8

プログラマーは、使用している言語に精通している必要があると思います。明確に使用してください。そしてそれをうまく使います。私彼らが彼らが使っている言語を人為的に無効にするべきではないと思います。私は経験から話します。私はかつて文字通りCobolショップの隣で働いていましたが、「あまりに複雑だったので」彼らはELSEを使用しませんでした。Reductio ad absurdam。


@downvoter魅力的です。あなたはプログラマーが言語で有能であるべきだと思いませんか?あなたは彼ら人工的に言語を無効にするべきだと思いますか?どっち?ここには3番目の選択肢はありません。
ローン侯爵2013

1
そして、そのCOBOLの例に対する賛成票は、コーディングに入る前にCOBOLがほとんど消滅したことをさらに嬉しく思います
Mark K Cowan

1
@MarkCowan理由はわかりません。私のポイントは、言語ではなく、恣意的な制限についてです。逸話はCobolに何か問題があることを示していません。死ぬ必要があったのは、そのプログラミングショップでした。大きな銀行の内臓を訪ねると、Cobolが健在であることがわかります。
ローン侯爵、2015

2

既存の回答のいくつかで言及されているように(不愉快なことに私はコメントできません)、問題はx ++ ++ xが異なる値に評価されることです(インクリメントの前と後)。これは明白ではなく、非常に混乱する可能性があります- もしその値が使用されます。cdmckayは、インクリメント演算子の使用を許可することを非常に賢明に提案していますが、返された値が使用されない方法でのみ(たとえば、独自の行で)使用されます。また、forループ内での標準的な使用法も含めます(ただし、戻り値が使用されない3番目のステートメントのみ)。他の例は思いつきません。自分で「焼き尽くされた」ので、他の言語にも同じガイドラインをお勧めします。

この厳しすぎるのは、多くのJSプログラマーが経験の浅いためであるという主張には同意しません。これは、「過度に賢い」プログラマの典型的な典型的な記述であり、従来の言語や、そのような言語にバックグラウンドを持っているJS開発者には、はるかに一般的だと思います。


1
返信いただきありがとうございます。コメントするには、50以上のレピュテーション値が必要です。参照:stackoverflow.com/faq
artlung

1
@artlung:私はそれとその背後にある理論的根拠を知っています。私はそれが迷惑だと言っていただけです:)
プリブマン近辺

2

私の経験では、++ iまたはi ++は、オペレーターの動作について最初に学習したとき以外、混乱を引き起こしたことはありません。これは、演算子を使用できる言語で教えられている高校または大学のコースで教えられる最も基本的なforループとwhileループに不可欠です。個人的には、a ++が別の行にある場合よりも見やすく読みやすいように、以下のようなことをしているのを見つけました。

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

結局のところ、それはスタイル設定であり、それ以上ではありません。より重要なのは、コードでこれを行うときに一貫性を保ち、同じコードで作業している他の人が従うことができ、同じ機能を異なる方法で処理する必要がないことです。 。

また、Crockfordはi- = 1を使用しているようです。これは--iやi--よりも読みにくいと思います。


2

私の2セントは、次の2つの場合には回避すべきだということです。

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

このような例では、変数が自動インクリメント/デクリメントされることを簡単に見逃すか、最初のステートメントを削除することさえできます。言い換えると、非常に短いブロックでのみ、または変数がブロック内の2、3のcloseステートメントでのみ表示される場所でのみ使用します。

2)同じステートメント内の同じ変数について、++と-が複数ある場合。このような場合に何が起こるかを覚えることは非常に困難です:

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

試験や専門的なテストでは、上記のような例について尋ねられます。実際、私はそれらの1つに関するドキュメントを探しているときにこの質問に出くわしましたが、実際には、1行のコードについてそれほど考えさせる必要はありません。


1

FortranはCのような言語ですか?++も-もありません。これがループの書き方です

     integer i, n, sum

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

インデックス要素iは、ループを通過するたびに言語ルールによって増分されます。たとえば、1以外の値でインクリメントしたい場合、たとえば2だけ逆にカウントすると、構文は...

      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 ... ]

したがって、この2つの代替案の初歩的な調査に基づいて、言語設計者は、使用例を予測して代替構文を提供することで、++と-を回避できます。

FortranとPythonは、++と-を持っている手続き型言語よりもバグマグネティックの点で顕著ですか?証拠はありません。

私はFortranとPythonはCに似ていると主張します。90%の精度で難読化されていないFortranまたはPythonの意図を正確に推測できなかったCに堪能な誰かに会ったことがないためです。


1
ええ、IFortranは1950年代のものですが、Cは1970年代のものです。plusplus演算子のようなものが欠けているPythonの例は興味深いものです。データ構造の繰り返し処理は、Pythonではより明確に処理され、Pythonは「異なる」オブジェクト指向のJavaScriptです。おそらく、Crockfordのアドバイスは、反復にOOに似た構文を使用することについてです。
artlung 2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.