シーケンスステートマシン

Aug 23 2020

コマンドライン引数を解析しようとしました。プログラムには4つの引数が必要です。私は議論を繰り返します。引数がオプションの場合、オプションを処理します。それ以外の場合、引数は必須引数の1つです。必要な引数を読み取るには、ある種のステートマシンが必要です。最初のケースでは、最初の引数を読み取る必要があります。2番目のケースでは、2番目の引数などです。

私が書いたProc再び返すだけで1つのメソッドを持つクラスをProcクラスを。

static abstract class Proc {
  abstract Proc exec (String arg);
}

これにより、いくつかのアクションを実行し、次に何をすべきかを定義できます。

  1. データベースホストを保存してから名前を読み取る
  2. データベース名を保存してからユーザーを読み取る
  3. dbユーザーを保存してからxmlファイルを読み取ります
  4. xmlファイルを保存してから何も保存しない

しかし、すべてのクラスオーバーヘッドのため、読みにくいです。

Proc proc = new Proc () {
    Proc exec (String arg) {
      db_host = arg;
      return new Proc () {
        Proc exec (String arg) {
          db_name = arg;
          return new Proc () {
            Proc exec (String arg) {
              db_user = arg;
              return new Proc () {
                Proc exec (String arg) {
                  xml_file = arg;
                  return null;
                }
              };
            }
          };
        }
      };
    }
  };

コードを単純化する方法はありますか?Lambdasを試しましたが、Lambdasは最終変数しか使用できないようです。これは、値を格納するときに少し役に立たないものです。

完全な例:

public class Import
{
  static String db_host = null;
  static String db_port = "5432";
  static String db_name = null;
  static String db_user = null;
  static String xml_file = null;

  static void usage ()
  {
    System.err.println ("Usage: Import [-p PORT] HOST DATABASE USER FILE");
  }

  static abstract class Proc {
    abstract Proc exec (String arg);
  }

  static void parse_args (String[] args)
  {
    Proc proc = new Proc () {
        Proc exec (String arg) {
          db_host = arg;
          return new Proc () {
            Proc exec (String arg) {
              db_name = arg;
              return new Proc () {
                Proc exec (String arg) {
                  db_user = arg;
                  return new Proc () {
                    Proc exec (String arg) {
                      xml_file = arg;
                      return null;
                    }
                  };
                }
              };
            }
          };
        }
      };

    try {
      for (int i = 0; i < args.length; i++)
        switch (args[i]) {
        case "-p":
          db_port = args[++i];
          break;
        case "-h":
          usage ();
          break;
        default:
          proc = proc.exec (args[i]);
        }
    }
    catch (Exception ex) {
      throw new Error ("Can not parse args!", ex);
    }
  }

  public static void main (String[] args)
  {
    parse_args (args);

    System.err.println ("db_host: " + db_host);
    System.err.println ("db_port: " + db_port);
    System.err.println ("db_name: " + db_name);
    System.err.println ("db_user: " + db_user);
    System.err.println ("xml_file: " + xml_file);
  }
}

回答

5 Bobby Aug 23 2020 at 23:46

まず、Javaの命名規則に従う必要があります。


第二に、できるという理由だけで名前を短くしないでください。たとえば、「Proc」は何の略ですか?手順?プロセッサー?名前が長くなっても読みやすさはそれだけの価値があります!


static String db_host = null;

メンバーは、のようなstaticものであってはならず、理想的にはpackage-private、のような、protectedまたはprivate意図を明確に示す以外の資格を持っている必要があります。


必要な引数を読み取るには、ある種のステートマシンが必要です。

それはあなたが与えられた要件ですか、それともあなたが持っていた仮定ですか?それはまったく真実ではないからです。全体として、システムは完全に不必要に複雑であり、明白な理由や利点はありません。ステートマシンのようにも見えません。関数呼び出しの非常に複雑なチェーンにすぎません。

コマンド引数解析用のステートマシンは次のようになります。

for (int index = 0; index < args.length; index++) {
    String arg = args[index];
    
    if (arg.equals("-p") && databaseHost == null) {
        arg = args[++index];
        
        databasePort = arg;
    } else if (databaseHost == null) {
        databaseHost = arg;
    } else if (databaseUsername == null) {
        databaseUsername = arg;
    } else if (databasePassword == null) {
        databasePassword = arg;
    } else if (inputFile == null) {
        inputFile = arg;
    }
}

そしてそれでさえ奇妙に複雑です。

必要なのは、機能する最も単純なソリューションであり、それは引数のハードコードされた抽出です。

if (args[0].equals("-h")) {
    printHelp();
    return;
}

int portProvidedOffset = 0;

if (args[0].equals("-p")) {
    databasePort = args[1];
    
    portProvidedOffset = 2;
}

databaseHost = args[portProvidedOffset + 0];
databaseUsername = args[portProvidedOffset + 1];
databasePassword = args[portProvidedOffset + 2];
inputFile = args[portProvidedOffset + 3];

もちろん、これはきれいではありませんが、回避できる最も簡単なソリューションです。もちろん、配列の長さが4要素以上であるかどうかを事前に確認する必要があります。その後、すべてのパラメータが設定されているかどうかを確認し、設定されていない場合は、エラーで終了します。

より洗練されたソリューションが必要な場合は、完全な引数パーサーを自分で作成する必要があります。これはそれほど簡単なことではありません。