[図書][モダンジャワ動作]再構築、テスト、デバッグ


読み取りと柔軟性を向上させるために再設計


コード可読性の向上


コードの可読性が良いことは、どのコードも簡単に他の人に理解されることを意味します.すなわち、コードは毒性を改善し、コードの理解とメンテナンスを容易にすることができます.

匿名クラスをRamda式に再パッケージ


抽象的な方法を実装する匿名クラスはRamda式で再パッケージすることができる.(匿名クラスをramda式に変換することを推奨します)ただし、すべての匿名クラスがramda式に変換できるわけではありません.変換は以下の情報を参考にする必要があります.

  • 匿名クラスで使用されるthisとsuperはramda式で異なる意味を持つ.匿名クラスでは、これは自分を指すが、ランダではランダをめぐるクラスを指す.

  • 匿名クラスは、囲まれたクラス変数を区別できます.しかし、ラムダ式では変数を区別できない.

  • 匿名クラスをramda式に変換すると、コンテキストがオーバーロードされてぼやけます.匿名クラスはインスタンス化時に明示的にフォーマットされますが、Ramdaのフォーマットはコンテキストによって異なります.(明示的な変換が必要です.)
  • Ramda式をメソッド参照として再パッケージ


    メソッドリファレンスを使用すると、可読性が向上し、コードの意図が明確になります.Ramda式を個別のメソッドとして抽出し、メソッド参照に変更できます.
    静的ヘルプ(比較、maxByなど)も使用できます.

    コマンド型データ処理をストリームにリダイレクト


    理論的には、反復器を用いたすべての既存の集合処理コードをストリームAPIに変換することができる.
    コマンド型コードのbreak,continue,returnなどの制御フロー文を解析し,同じ機能を持つフロー演算にプッシュする必要があるため,これは容易なことではないが,いくつかのツールの助けを得ることができる.

    コードの柔軟性の向上


    条件付きの延期
    クライアントコードでオブジェクトのステータスをチェックしたり、オブジェクトのいくつかのメソッドを呼び出したりする場合は、ramdaまたはメソッドリファレンスをパラメータとして使用し、内部で新しいメソッドを使用してオブジェクトのステータスをチェックし、メソッドを呼び出すことが望ましい.毒性が良くなり、カプセル化も強化される.
    ラウンドの実行
    毎回同じ準備・終了プロセスを繰り返し実行するコードがあればramdaに変換できます.処理準備,終了プロセスの論理を再利用することで,コード重複を低減できる.

    ランダでオブジェクト向けのデザインモードを再設計


    デザインモードは再利用可能な部品です.設計モードでRamda式を使用すると、より簡単に問題を解決できます.既存のオブジェクト向けの多くの設計モードを削除したり、簡潔に再実現したりすることができます.

    戦略モデル


    これは、1つのアルゴリズムがある場合に、実行時に適切なアルゴリズムを選択する方法である.戦略モデルは3つの部分に分かれている.
    1)アルゴリズムを表すインタフェース
    2)インタフェースの実装体
    3)ポリシー・オブジェクトを使用する1つまたは複数のクライアント
    Ramda式を使用すると、インプリメンテーションをクラスとして作成することなく、直接渡すことができます.
    Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
    boolean b1 = numericValidator.validate("aaaa");

    テンプレートメソッド


    アルゴリズムの概要が与えられた後、一部のアルゴリズムを修正する柔軟性が必要な場合は、テンプレートメソッドを使用してモードを設計します.(アルゴリズムを使用するときに少し変更する必要がある場合)
    修復したいメソッドのフラグと一致する関数型インタフェースをパラメータとして受け入れる.
    public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(c);
    }
    これにより,Ramda式を継承されずに動作を追加することができる.
    new OnlineBankingLambda().processCustomer(1337, (Customer c) -> System.out.println("Hello " + c.getName()));

    傍観者モード


    イベントが発生した場合、あるオブジェクトが別のオブジェクトのリストを自動的に通知する必要がある場合に使用します.
    複数の光ファイバを組み合わせたObserverインタフェースが必要です.Observerインタフェースは、通知方法を提供します.
    interface Observer {
        void notify(String tweet);
    }
    このインタフェースを必要なアクションとして実装します.
    class NYTimes implements Observer {
        public void notify(String tweet) {
            if (tweet != null && tweet.contains("money")) {
                System.out.println("Breaking news in NY! " + tweet);
            }
        }
    }
    Observerインプリメンテーションを登録し,Observerインプリメンテーションに通知する役割を果たすSubjectを実現する.
    interface Subject {
        void registerObserver(Observer o);
        void notifyObservers(String tweet);
    }
    class Feed implements Subject {
        private final List<Observer> observers = new ArrayList<>();
        public void registerObserver(Observer o) {
            this.observers.add(o);
        }
        public void notifyObservers(String tweet) {
            observers.forEach(o -> o.notify(tweet));
        }
    }
    Feed f = new Feed();
    f.registerObserver(new NYTimes());
    ...
    これを減らすにはramda式を使用します.Observerのインプリメンテーションをクラスとしてすぐに作成する必要はなく、Ramda式を渡すことができます.
    f.registerObserver((String tweet) -> {
        if (tweet != null && tweet.contains("money")) {
            System.out.println("Breaking news in NY! " + tweet);
        }
    });

    義務チェーン


    ジョブ処理オブジェクトチェーンを作成するときに、義務チェーンモードを使用します.あるオブジェクトはアクションを処理し、結果を別のオブジェクトに渡し、別のオブジェクトもそのアクションを処理し、結果を別のオブジェクトに渡します.
    通常、タスク処理抽象クラスは、処理するオブジェクト情報を保持するフィールドを含み、義務チェーンモードを構成する.
    public abstract class ProcessingObject<T> {
        protected ProcessingObject<T> successor;
        
        public void setSuccessor(ProcessingObject<T> successor) {
            this.successor = successor;
        }
        
        public T handle(T input) {
            T r = handleWork(input);
            if (successor != null) {
                return successor.handle(r);
            }
            return r;
        }
        abstract protected T handleWork(T input);
    }
    handleメソッドは、特定のタスクをどのように処理するかを記述し、Processing Objectクラスを継承し、handleWorkメソッドを実装することで、さまざまなタスク処理オブジェクトを作成できます.
    作成したタスク処理オブジェクトを関連付けて使用できます.
    public class HeaderTextProcessing extends ProcessingObject<String> {
        public String handleWork(String text) {
            return "From Raoul, Mario and Alan: " + text;
        }
    }
    public class SpellCheckerProcessing extends ProcessingObject<String> {
        public String handleWork(String text) {
            return text.replaceAll("labda", "lambda");
        }
    }
    このようにして作成されたジョブ処理オブジェクトを接続して使用します.
    ProcessingObject<String> p1 = new HeaderTextProcessing();
    ProcessingObject<String> p2 = new SpellCheckerProcessing();
    p1.setSuccessor(p2);
    
    String result = p1.handle("Aren't labdas really sexy?!!");
    
    これをramda式の組合せに簡略化できます.andThenメソッドを使用します.
    UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
    
    UnaryOperator<String> SpellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
    
    Function<String, String> pipeline = headerProcessing.andThen(SpellCheckerProcessing);
    
    String result = pipeline.apply("Aren't labdas really sexy?!!");

    こうじょう


    インスタンス化ロジックをクライアントに露出させることなく、オブジェクトの作成時にファクトリ設計モードを使用します.作成者や設定を暴露しないことで、クライアントは簡単に商品を生産することができます.
    public static Product createProduct(String name) {
        switch (name) {
            case "loan":
              return new Loan();
            case "stock":
              return new Stock();
            case "bond":
              return new Bond();
            default:
              throw new RuntimeException("No such product " + name);
        }
    }
    上のコードはこのように変更できます.
      final static private Map<String, Supplier<Product>> map = new HashMap<>();
      static {
        map.put("loan", Loan::new);
        map.put("stock", Stock::new);
        map.put("bond", Bond::new);
      }
      
      public static Product createProductLambda(String name) {
          Supplier<Product> p = map.get(name);
          if (p != null) {
            return p.get();
          }
          throw new RuntimeException("No such product " + name);
      }
    

    ランダしけん


    コードを簡潔に書くことも重要ですが、正しい動作コードが重要です.このために単位テストを行います.Ramda式自体をテストするよりも、Ramda式を使用する方法の動作をテストしたほうがいいです.

    表示されるRamda式の動作テスト


    ラムダ式は関数型インタフェースの例を生成する.したがって、ramda式は、生鮮なインスタンスの動作でテストすることができる.

    ランダの方法を使う動作に集中します。


    ラムダの目標は、他の方法で使用するために、既定の動作を破片にカプセル化することである.したがって,詳細な実装を含むRamda式は公開しない.ramda式を含む方法がある場合は、ramda式の検証の代わりに、この方法をテストします.

    複雑なランダを個別に分割する方法


    多くの論理を含む複雑なRamda式が存在する場合、Ramda式は、通常の方法をテストするようにRamda式をテストする方法参照(新しい通常の方法宣言)に変換することができる.

    高次元関数テスト


    方法がランダをパラメータとして受け入れる場合,他のランダ試験方法の動作を用いることができる.複数のランダ食品を方法に渡し、その結果値をテストします.

    デバッグ


    コードをデバッグするときは、次の2点を確認する必要があります.
  • スタックペギング
  • 記録
  • スタックトラッキングチェック


    プログラムが異常発生で中断した場合は、プログラムがどこで停止しているかを確認する必要があります.この情報はスタックフレームにあります.メソッドを呼び出すたびに、呼び出し位置、呼び出し時の引数、呼び出しメソッドを含む領域変数などの情報が生成され、スタックフレームに格納されます.
    したがって、フレーム毎にプログラムが停止したときにどのように停止したかを表示するスタックトラッキングが得られる.(メソッド呼び出しリスト)
    しかしラムダ式には名前がないため,複雑なスタック遡及が生じる.したがって、例外が発生した場合、コンパイラは名前を作成します.
    // 예시
    lambda$main$0 
    多くの蘭茶食品が含まれていると、認識しにくい.メソッド参照を用いても認識しにくい名前がスタックペギングを生成する.(メソッド参照のクラスと同じ場所でメソッド参照を使用すると、正しい名前が表示されます)

    レコード情報


    パイプライン演算をデバッグする際にforEachなどを使って出力を試みることができます.しかしforeachが呼び出された瞬間、ストリーム全体が消費され、中間演算の結果は確定しない.
    このときpeekという演算を使うことができます.peekが実行する操作は、ストリーム内の各要素を消費するように行われますが、実際には要素を消費しません.
    List<Integer> result = Stream.of(2, 3, 4, 5)
        .peek(x -> System.out.println("taking from stream: " + x))
        .map(x -> x + 17)
        .peek(x -> System.out.println("after map: " + x))
        .filter(x -> x % 2 == 0)
        .peek(x -> System.out.println("after filter: " + x))
        .limit(3)
        .peek(x -> System.out.println("after limit: " + x))
        .collect(toList());
    }