web-dev-qa-db-ja.com

JavaScriptのインクリメント( "++")およびデクリメント( " - ")演算子を避けるのはなぜですか?

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

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

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

"++"と "--"の構文を欠いている、あるいはそれとは異なる方法で類似した言語がいくつかあるのか、あるいは私が見逃しているかもしれない "++"と "--"を避けることの理由

343
artlung

私の見解では、++と - を単独で1行で使用します。

i++;
array[i] = foo;

の代わりに

array[++i] = foo;

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

388
cdmckay

私はそのアドバイスに率直に混乱しています。私の一部は、それがジャバスクリプトコーダに関する経験の欠如(知覚されたまたは実際の)ともっと関係があるかどうか疑問に思います。

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

246
Jon B

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

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

文字列をコピーするには、おそらくこれが彼が言及している過度のトリックの原因です。

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

++i = i++;

または

i = i++ + ++i;

実際に行います。それはいくつかの言語で定義されており、他の言語では何が起こるかについての保証はありません。

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

68
Eclipse

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

Crockfordは、jsLintの自動インクリメントと自動削除を許可しないオプションを作成しました。あなたはアドバイスに従うかどうかを選択します。

私自身の個人的な規則は、オートインクリメントやオートデクリメントと組み合わせて何もしないことです。

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

それで、私自身の規則では、forループの増分としてi ++を使用するのは問題ありません。

45
Nosredna

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

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を加えるより良い方法があります。

参考文献

25
Paul Sweatte

次のコードを見てください

    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ループは++でも問題ないと思いますが、同じ行または同じ命令に複数の++記号がある場合は注意してください。

17
Eric

インクリメント演算子とデクリメント演算子の「前」と「後」の性質は、それらに慣れていない人にとっては混乱を招きがちです。それは彼らがトリッキーになることができる一つの方法です。

16
Paul Sonier

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

16
Sriram

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

効率のために、私は好きです:

  • ++ i戻り値を使わないとき(一時的ではありません)
  • i ++を使用している場合戻り値(パイプラインの停止なし)

私はCrockford氏のファンですが、この場合私は反対しなければなりません。 ++ii+=1よりも25%少ない解析テキストで、間違いなく明確です。

13
Hans Malherbe

私はこれについてDouglas Crockfordのビデオを見てきました、そしてインクリメントとデクリメントを使わないという彼の説明はそれです

  1. これまで他の言語では、配列の境界を破り、あらゆる面で悪意のある問題を引き起こしてきました。
  2. それがより混乱し、経験の浅いJS開発者であることは正確にそれが何をするのかわからない。

まず、JavaScriptの配列は動的にサイズ変更されるため、間違っている場合はご容赦ください。JavaScriptのこのメソッドを使用してアクセスできないデータにアクセスすることはできません。

第二に、複雑なことを避けなければなりません、確かに問題はこの機能があるということではありませんが、問題はJavaScriptを実行すると主張する開発者がいることです。とても簡単です。 value ++、現在の値を返してください。式の後に現在の値を追加してください。++ valueは、入力する前に値を増やしてください。

A ++ + ++ 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を裏返しに知っているチームがあれば、心配する必要はありません。そうでなければそれをコメントし、それを別の方法で書くなど。あなたがしなければならなかったことをしなさい。インクリメントとデクリメントが本質的に悪い、バグを発生させる、脆弱性を発生させるなど、視聴者によっては読みやすさが低下する可能性があります。

ところで、私はとにかくダグラス・クロックフォードは伝説だと思いますが、彼はそれに値しない演算子をめぐる多くの恐怖を引き起こしたと思います。

私は間違っていると証明されるように生きる...

13

もう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
}
10
Evgeny Levin

プログラマは彼らが使っている言語に精通しているべきだと思います。はっきりと使ってください。そしてそれを上手に使ってください。 Idon't彼らは人工的に彼らが使っている言語を不自由にするべきだと思います。私は経験から話します。私はかつて文字通り、彼らがELSEを使っていないCobolの店の隣で働いた。絶対数の減少

8
user207421

私の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のクローズステートメントでブロック内の変数が現れる場所でのみ使用してください。

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

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

試験とプロのテストは上記のような例について尋ねます、そして、確かに私はそれらのうちの1つに関するドキュメンテーションを探している間この質問につまずきました、しかし実際には1行のコードについてそんなに考えることを強制されるべきではありません。

2
zakmck

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

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

結局それはスタイルの好みであり、それ以上のことではありません。もっと重要なことはあなたがコードの中でこれをしても同じコードを扱う他の人が従うことができるように異なる方法で同じ機能を処理する必要がないということです。

また、Crockfordは、i- = 1を使用しているようです。これは、 - iまたはi--よりも読むのが難しいことがわかりました。

2
burdz

これが彼の推論の一部であったかどうか私は知りませんが、あなたが不十分に書かれた縮小化プログラムを使用するなら、それはx++ + yx+++yに変えることができます。しかし、やはり、よく書かれていないツールでは、あらゆる種類の混乱が引き起こされる可能性があります。

2
James M.

いくつかの既存の回答で述べたように(これは煩わしく私がコメントすることができません)、問題はx ++ ++ xが異なる値に評価されることです(インクリメントの前と後)。 その値が使用されている場合。 cdmckayは、インクリメント演算子の使用を許可することを非常に賢明に提案しますが、戻り値が使用されないような方法でのみです。それ自身の行に。私はまたforループ内の標準的な使用法を含みます(しかし戻り値が使用されていない3番目のステートメントでのみ)。他の例は考えられません。私は「焼け」ていたので、他の言語にも同じガイドラインをお勧めします。

私は、この過度の厳しさが多くのJSプログラマーが経験不足であることが原因であるという主張に反対します。これはまさしく「過度に賢い」プログラマの典型的な書き方であり、もっと伝統的な言語で、そしてそのような言語のバックグラウンドを持っているJS開発者でずっと一般的であると確信しています。

2
Near Privman

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