web-dev-qa-db-ja.com

多くの独立した条件に基づいて、もしそうでなければクラッターのためのアプローチをリファクタリングする

現在、私は維持とテストが難しい状態でクリーンアップしており、他に孤立してチェックする必要がある条件に基づいているかどうかをテストしています:

条件の基本的な意味は何ですか?

Big Entity Objectsは、以下の例のように、状態変更のために2つのエンティティキー、つまりTransとRightに基づいてチェックする必要があります。

if (oldTrans.getfOrder().equals(newTrans.getfOrder()) {
     compound.setIsStateChanged(true);
      return;
}
if (oldRight.getgRight().equals(newRight.getgRight()) {
     compound.setIsStateChanged(true);
}
  ....and the list goes on till 20 more such conditions

現在、if elseはすべて1か所に散らばっています。

    if (oldTrans.getfOrder().equals(newTrans.getfOrder()) {
        compound.setIsStateChanged(true);
        LOGGER.info("major change detected");
        return compound;
    } if (oldTrans.getgOrder().equals(newTrans.getgOrder()) {
        compound.setIsStateChanged(true);
LOGGER.info("major change detected");
        return compound;   
    }

ここに2つの主な問題があります

  1. すべてのifにはreturnステートメントがあり、ifが非常に多いため、いつどのポイントメソッドが終了するかを知るのは困難です。

  2. 分岐がエラーを起こしやすく、条件の数が増える可能性がある場合、多くの人に。

クリーンなコードの観点から、基本的に同じセマンティクスに基づいている非常に多くのifを回避するために、多態的な方法で解決しようとしました

列挙型の条件を定数として抽出し、新しいオブジェクトと古いオブジェクトをパラメーターとして受け取るチェッカーインターフェイスを実装する

    public interface ICheckStateChange<T>(T old, T new) {
        boolean check(T old, T new);
    }

    //implementations
    public TransChecker implements ICheckStateChange<Trans> {

      List<BiPredicate<Trans, Trans>> allTransConditions = transConditions.getValues();

    public boolean check(Trans oldTrans, Trans newTrans) {
         //all conditions null check here
        //loop through conditions
        for (BiPredicate<Trans, Trans> transCondition: allTransConditions) {
            if (transCondition).test()) {
                return true;
             LOGGER.info("major state change detected, taking apt action")
          }
    }

public RightChecker implements ICheckStateChange<Right> {

      List<BiPredicate<Right, Right>> allTransConditions = RightConditions.getValues();

    public boolean check(Right oldRight, Right newRIght) {
         //all conditions null check here
        //loop through conditions
        for (BiPredicate<Right, Right> rightCondition: allRightConditions) {
            if (rightCondition).test()) {
                return true;
             LOGGER.info("major state change detected, taking apt action")
          }
    }

条件は、ラムダを使用してBiPredicate定数として中央に配置されるようになりました。

public enum rightConditions {
    FORDER_CHANGE_NULL_TO_NOT_NULL((Order old, Order new)
       -> old == null && new != null),

    //to be replaced by the right condition
    GORDER_CHANGE_FROM_OPEN_TO_DONE((Order old, Order new)
       -> old == null && new != null)

    //to be replaced by the right condition
    LORDER_CHANGE_FROM_OPEN_TO_REVERTED((Order old, Order new)
       -> old == null && new != null)
   }

ここでの私の質問は、クリーンなコードを後から見たラムダBiPredicatesの助けを借りてIf elsesをリファクタリングするアプローチについてです?読みやすさ、拡張性、保守性;)

2
Anirudh

オプションについて議論しましょう。あなたが却下しているように見えるオプションは、実際には本当にきれいなものです:

public void resolveChanges() {
    boolean isChanged = false;
    isChanged = isChanged || oldTrans.getfOrder().equals(newTrans.getfOrder());
    isChanged = isChanged || oldRight.getfRight().equals(newRight.getfRight());
    // etc.

    compound.setIsStateChanged(isChanged);
}

これは、以前のメンテナンスの悪夢やユニットテストの悪夢ではありません。以前にあったすべての条件はまだそこにあります。ただし、すぐに読みやすくなります。ブールショートカットはまだ有効です。

注:ここにコードをコピーして貼り付けましたが、あるものは別のものと同等であり、変更を主張するのは間違っているようです...しかし、コードベースがわかりません。


リファクタリングに沿って、ラムダを使用します。基本的に、すべてのラムダを配列に設定し、それらを反復処理します。これに関する問題は、必ずしもコードの残りの部分と同じ場所にあるとは限らない設定が少しあるため、それに従うことは少し難しいでしょう。

public interface Predicate {
    boolean test();
}

public class TheBigClass {
    private final static Predicate[] resolvers = new Predicate[] {
        () -> oldTrans.getfOrder().equals(newTrans.getfOrder()),
        () -> oldRight.getfRight().equals(newRight.getfRight())
    }

    public void resolveChanges() {
        for (Predicate p : resolvers) {
            if (p.test()) {
                compound.setIsStateChanged(true);
                return;
            }
        }
    }
}

または、そのようないくつかのバリエーション。繰り返しになりますが、個々のテストはほぼ同じであり、単体テストの作成の複雑さは最初のコードからまったく変わりません。

2
Berin Loritsch

例外の詳細なライン情報が必要な場合:

bool changeDetected = false;

changeDetected = changeDetected || oldTrans.getfOrder().equals(newTrans.getfOrder());
changeDetected = changeDetected || oldTrans.getgOrder().equals(newTrans.getgOrder());

if (changeDetected)
{
    compound.setIsStateChanged(true);
    LOGGER.info("major change detected");
}
return compound;

そうでなければ、タイピングを少なくしたい場合:

if (   oldTrans.getfOrder().equals(newTrans.getfOrder())
   ||  oldTrans.getgOrder().equals(newTrans.getgOrder())
{
    compound.setIsStateChanged(true);
    LOGGER.info("major change detected");
}
return compound;

それらのチェックが本当にあなたを煩わしくして、あなたがそれをまっすぐに保つためにきれいな名前が必要な場合...

bool hasfOrderChanged(Object oldTrans, Object netTrans)
{
    return oldTrans.getfOrder().equals(newTrans.getfOrder())
}
bool hasgOrderChanged(Object oldTrans, Object netTrans)
{
    return oldTrans.getgOrder().equals(newTrans.getgOrder())
}

...snip...
if (   hasfOrderChanged(oldTrans, newTrans)
   ||  hasgOrderChanged(oldTrans, newTrans)
{
    compound.setIsStateChanged(true);
    LOGGER.info("major change detected");
}
return compound;
1
Kain0_0