web-dev-qa-db-ja.com

`switch`ステートメントは一般的に間違っていますか?

ほとんどの開発者は、すべてのswitchbreaksを含むcaseステートメントを使用しています。 if-elseステートメントの単純なセットで十分なので、switchステートメントの使用は不適切ですか?

switchを使用する唯一の理由は、2つ以上のcasesが関連しているためだと考えてもかまいません。したがって、意図的にいくつかのステートメントを「フォールスルー」させたいと思いますか?

switchを使用しても問題ないと思う例は、データベースのスキーマを更新する場合です。したがって、バージョン1からバージョン4に更新する場合は、スキーマがバージョン2と3を最初に通過するようにする必要があります。

private void updateDatabase(int oldVersion){
   switch(oldVersion){
      case 1:
         updateToVersion2();
      case 2:
         updateToVersion3();
      case 3:
         updateToVersion4();
   }
}

switchのNOT OKの例は、ビデオゲームでそれを使用して、押されたボタンに基づいてキャラクターを移動する場合です。 breakを押すと文字が全方向に移動しないように、caseを各LEFTする必要があります。

private void onButtonPressed(Button button){
   switch(button){
      case LEFT:
         moveLeft();
         break;
      case RIGHT:
         moveRight();
         break;
      case UP:
         moveUp();
         break;
      case DOWN:
         moveDown();
         break;
    }
 }
3
AxiomaticNexus

いいえ、switchステートメントは通常間違って使用されません。

これらは主に使用目的に使用されます。可能な入力値の小さめのセットの代替アクションを列挙します。長いif/elseチェーンよりも読みやすく、チェックされた値が適度に隣接している場合、コンパイラーは非常に効率的なコードを出力でき、代替を忘れたときに警告するエラーチェックコンパイラーを書くのが簡単です。これはすべてうまくいきます。 (継承と動的ディスパッチが望ましい場合にswitchを使用することは、OOPをまだ十分に理解していない初心者にとってよくある間違いですが、それはより高いレベルの問題です。 )

ただし、switchで定義されていますほとんどの言語では不適切です。

最も一般的な使用例は、ケースごとに異なることを行うことであるため、フォールスルーではない各ケースの後にbreakを記述するのは面倒で煩雑です。 switch本体の後のデフォルトがbreakであり、明示的なcaseキーワードを介してフォールスルーを注文する必要がある場合、fallthroughステートメントはより適切に機能します(2つのcasesが直接隣接していない限り)。それは多くの不要な行を削除し、多くの人はbreakを忘れて微妙なエラーを犯すことはなかったでしょう。

しかし、それについて何かをするのは今では間違いなく手遅れです。 Cはそのようにしており、その子孫のどれもそれについて何かを変更するのに十分勇敢でなかったので、当分の間、switchはおそらくそのままです。

5
Kilian Foth

私はあなたの言っていることを理解していますが、これは最終的に不適切ではありません。

elsesが1つまたは2つだけの場合は、if-elseの変更で十分です。しかし、それ以上になると、switchステートメントはすぐに、よりクリーンで見た目が悪くなります。または...あなたが個人的に誰であるかに依存して、それはそれをさらに厄介なものに変える可能性があります。

常にすべてを打ち破る場合は厳密に表面的なものですが、特に悪いと言うことは、/*...*/コメントと//コメントの間で、一方が他方よりも完全に優れていると言うようなものです。長いif-elseチェーンに問題がない場合、より簡単なswitchステートメントの何が問題になっていますか?この質問が当てはまる場合、それは表面的なものであり、フェンスの両側にきちんと見える人がいます。これに関して大きな決まりはありません。

ただし、if-elseチェーンだけを使用するのではなく、すべてから抜け出すswitchステートメントに何か問題がある場合、switchステートメントを正当化するために何があるのですか? tすべてから抜け出す?最初の例を見てみましょう(私はBrandonに同意しますが、シャープでした!):

private void updateDatabase(int oldVersion){
    switch(oldVersion){
        case 1:
            updateToVersion2();
        case 2:
            updateToVersion3();
        case 3:
            updateToVersion4();
    }
}

これは次のように簡単に書くことができませんでした:

private void updateDatabase(int oldVersion){
    if (oldVersion > 0 && oldVersion < 4){
        if (oldVersion < 3){
            if (oldVersion < 2){
                updateToVersion2();
            }
            updateToVersion3();
        }
        updateToVersion4();
    }
}

これは化粧品的に良いですか悪いですか?それは意見の問題です。これはパフォーマンスの点で良いですか、悪いですか?技術的には、言語のコンパイル方法に応じて、導入されたintintの比較が1つ追加される可能性がありますが、実際には、高水準プログラミング言語でしょうか?実際には、おそらくoldVersion0より大きいことをすでに確認しているか、またはswitchステートメントを使用して同様のチェックを関数に追加する必要があります。

一部の人々はこれも考えています:

switch (true)
{
    case variable1:
        doSomething1();
        break;

    case variable2 && variable3:
        doSomething2();
        break;

    case !variable4 && variabl5:
        doSomething3();
        break;
        .
        .
        .
    default:
        doSomethingElse();
}

最終的には次のコードよりもクリーンなコードです。

if (variable1)
{
    doSomething1();
}
else if (variable2 && variable3)
{
    doSomething2();
}
else if (!variable4 && variable5)
{
    doSomething3();
.
.
.
}
else
{
    doSomethingElse();
}

(最後の2つのスニペットで中括弧が配置された場所の違いに注意してください。人々がこの方法でこれを行うと、特定の言語で進行中の議論の主題となりますが、switchステートメント。)

switchステートメントの些細なブレイクアウトブレイクアウトの使用には何の問題もありませんが、経験豊富なプログラマーは、あなたが何に反対しているかを理解できるはずです。

2
Panzercrisis

一部の言語( Scala JVM、Haskell、Ocamlなどにコンパイルされています)には パターンマッチング があります。これは、データ構造を分解するため、switchより優れています。バインド変数(ローカルで一致する場合)。

たとえば、Ocamlでは、単純な [〜#〜] ast [〜#〜] データ型(asum typeまたは tagged union )は次のように表現します。

_type expr = 
    Num of int
   | Sum of expr * expr
   | Diff of expr * expr
   | Prod of expr * expr
  (* etc... *)
;;
_

次に、AST of 2*(3+4)

_   Prod (Num 2, Sum (Num 3, Num 4))
_

そしてあなたの再帰評価関数は

_let rec eval exp = 
  match exp with
   Num x -> x
   | Sum (el,er) -> (eval el) + (eval er)
   | Diff (el,er) -> (eval el) - (eval er)
   | Prod (el,er) -> (eval el) * (eval er)
   (* etc....*)
_

一致する変数elerのスコープは、_|_で始まる上記の行のように各一致句に限定されていることに注意してください(一致する操作によってバインドされます)。

コンパイラは通常、パターンマッチングをswitchのような構成(インデックス付きジャンプなど)とフィールド抽出に変換します。