web-dev-qa-db-ja.com

コードの臭いは壊れていますか?

ループについて質問しています。breakステートメントではswitchが重要です。 switchステートメント自体がコードのにおいであるかどうかは、別の問題です。

したがって、データ構造を反復するための次の使用例を検討してください。

  1. 構造全体に対して何かをしたい(休憩は不要)
  2. データ構造の一部に対して何かをしたい。
  3. データ構造で何かを検索したい場合(構造全体の反復が含まれる場合と含まれない場合があります)

上記のリストは、多かれ少なかれ私には網羅的であるようです。おそらくそこに何かが足りないのです。

ケース1はすぐに破棄できます。map/forEachを使用できます。ケース2は、filterまたはreduceが機能するように聞こえます。ケース3の場合、データ構造を反復して何かを見つける必要があるのは明らかな誤りであるように見えます。データ構造自体が関連するメソッドを提供する必要があるか、間違ったデータ構造を使用している可能性があります。

すべてのJavaScriptデータ構造(または実装)にこれらのメソッドがあるわけではありませんが、ほとんどすべてのJavaScriptデータ構造に関連する関数を書くのは簡単です。

調査中に this を見ましたが、それは明示的にC/C++のコンテキストにあります。 Cではそれが多かれ少なかれ必要になる方法を理解できましたが、私の理解は、C++にはラムダがあり、多くのデータ構造は一般にオブジェクトです(例:std::vectorstd::map)なので、なぜbreakが支持されているのか、なぜすべての回答がそれほど普遍的であるのか理解できませんが、C++について十分にコメントしているとは思えません。

また、一部の非常に大きなデータ構造では、構造全体を反復するコストが許容できないほど高くなる可能性があることも理解していますが、node.jsで作業する場合は非常に一般的ではありません。確かにそれはあなたがプロファイリングしたいものの一種です。

そのため、今日のJavaScriptにbreakの使用例がありません。ここで何か不足していますか?

6
Jared Smith

breakをループの外に出すことは、そのループを独自の関数にリファクタリングし、ガード句のreturnステートメントを出すことと同じです。

while(condition) {
  if(test) { break; }
  doStuff;
}

doMuchStuff();

function doMuchStuff() {
  while(condition) {
    if(test) { return; }
    doStuff;
  }
}

それらは事実上同じです。

単一のキーワードはコードのにおいではありません。それらはフロー制御のためのツールです。それらの多くは、スパゲッティコードの悪夢を回避するために、他の安全レベルでラップされたgotoのバリエーションです。

コードの特定のビットに問題があるかどうかを判断するには、そのコードがどのように機能するか、またそれが言語構造に適しているかどうかを調べる必要があります。キーワードだけでは、コードの匂いを確定するには不十分です。それは誤用される可能性があるという点で一風変わっているかもしれませんが、switchまたはbreakまたは他のフロー制御構成が逆効果であり、人々を妨げるスタイルガイドにつながるため、魔女狩りを続けるわかりやすく、簡単なコードを書くことから。

30
user40980

@MichaelTは、gotoを使用せずにgotoの C#サンプルコード を書き換える方法を尋ねました。ここに彼らのコードがあります:

using System;
class Test
{
   static void Main(string[] args) {
      string[,] table = {
         {"Red", "Blue", "Green"},
         {"Monday", "Wednesday", "Friday"}
      };
      foreach (string str in args) {
         int row, colm;
         for (row = 0; row <= 1; ++row)
            for (colm = 0; colm <= 2; ++colm)
               if (str == table[row,colm])
                   goto done;
         Console.WriteLine("{0} not found", str);
         continue;
   done:
         Console.WriteLine("Found {0} at [{1}][{2}]", str, row, colm);
      }
   }
}

最初に気付くのは、多くのサンプル/デモコードと同様に、結果を出力することです。 結果を出力するすべてのデモコードはB.S.です実際の関数はほとんど結果を出力しません。結果を返したり、オブジェクトを変更したり、ストリームに追加したりする必要があります...

(マイナーサイドトラック...)この場合、大したことはありませんが、3番目のネストされた非同期コールバックの奥深くにある「デモ」Node.jsコードを試してください。その結果がどのように使用されるのか、そしてデモ何かを印刷して問題を無視するだけです。うん。 :

しかし、それはあなたに考えさせます。そのコードは印刷中であり、複数のことを行っています。さて、私は単一責任原則について多くの人より熱狂的ではありませんが、マックに行き詰まり、後藤が見栄えしている場合、SRPに違反しています。

ああ、リファクタリングして、2D配列を検索するための汎用関数を書いたらどうなるの?これが最初の書き直しです:Note-私はC#プログラマではないので、これはJavaです。

public class Test {

    public static void Main(String[] args) {
          String[][] table = {
             {"Red", "Blue", "Green"},
             {"Monday", "Wednesday", "Friday"}
          };
          for (String str : args) {
             int[] found = indexOf2D(table, str);
             if (found == null)
                 System.out.println(str + " not found");
             else
                 System.out.println(str + " found at " + found);
       }
    }


    public static int[] indexOf2D(Object[][] array, Object grail) {
        for (int r = 0; r<array.length; r++) {
            Object[] row = array[r];
            for (int c = 0; c<row.length; c++)
                if (grail.equals(row[c]))
                    return new int[] {r,c};
        }
        return null;
    }
}

ここで、実際のプログラムでは、汎用の「1D配列での検索」関数を記述して、再度リファクタリングします。そして、null入力をテストします。あなたは、nullを返すか、それとも---( "Null Object" を返すか、オプションで返すかについて議論します。結局、スキルが何倍も高く、用途が広く、堅牢で、きれいに因数分解されたコードになります。

そして、コードはおかしなgotoを使用していません。前に述べたように、コードが複雑すぎてgotoを使用する必要がある場合、コードが複雑すぎます

「ああ、でも、2つのメソッド呼び出しとオブジェクト作成を追加したので、処理が遅くなります」ええと。これがたまにしか呼び出されない場合、追加の数ナノ秒は問題ではありません。そして、このコードがタイムクリティカルである場合、おそらくO(N) 2Dデータ構造の線形検索を使用することを再考する必要があります。:-)

break/continuereturnよりも「臭い」が多い理由に関するコメント。

Returnsは、プログラマが推論するのが簡単です。メソッドから抜けると、スタックとローカル変数はなくなります。実際には、そのメソッドの「クロージャのような」情報はすべて消えるので、心配する必要はありません。 (閉じなければならないファイルなどのリソースを開いていない限り...)

breakまたはcontinueを使用すると、コードを前にジャンプしますが、まだローカル状態を覚えておく必要があります。それは難しいかもしれません。どのローカル変数がまだスコープ内にありますか?ループの最後の値は何ですか?コードを読んでいるプログラマーは、その状態の「閉鎖」を最も頭に抱えています。

クロージャーは便利でクールですが、一般に、コンパイラーまたはインタープリターがそのすべての状態を保持するのに適しています。

2
user949300