web-dev-qa-db-ja.com

SonarQubeこのメソッドをリファクタリングして、認知の複雑さを軽減します

以下のユーティリティメソッドがあり、複数のifステートメントを使用しており、認識の複雑さの問題が発生しています。いくつかのリンクを経由しましたが、この方法のユーザーに影響を与えずにコードを変更する方法を理解できません。

public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken){

    String key=null;
    boolean isValidWrapper = false;

    if (wrapper != null && wrapper.length() > 7
        && wrapper.substring(0, 6).equalsIgnoreCase("XYZ"))
    {
        wrapper= wrapper.substring(7, wrapper.lastIndexOf('.')+1);
    }
    if(wrapper != null && wrapper.equalsIgnoreCase("TFR")) {
        isValidWrapper=Boolean.TRUE;
    }
    try {
         key = wrapper.getKey();
    }
    catch (Exception exception) {
        return isValidWrapper;
    }

    if(key!=null) {

        Date tokenExpiryTime = key.getExpiresAt();

        if(tokenExpiryTime!=null) {
            return isValidWrapper;
        }

        String algorithm=key.getAlgorithm();
        if(!DESIRED_ALGO.equals(algorithm)) {
            return isValidWrapper;
        }

        String value6=key.getType();
        if(!DESIRED_TYPE.equals(value6)) {
            return isValidWrapper;
        }


        if(key.getValue1()!=null && key.getValue2().size()>0 && key.getValue3()!=null && key.getValue4()!=null && key.getValue5()!=null) {
            isValidWrapper=Boolean.TRUE;
        }
    }

    return isValidWrapper;
}

このコードをリファクタリングするための提案を共有してください。

3
smruti ranjan

多くのif条件を1つにマージしたり、たとえば、いくつかの命令の順序を変更してコードをクリーンアップしたりしても、問題を解決できるとは思いません。

コードが 単一責任の原則 と一致しません。この大きなメソッドを小さな部分にリファクタリングする必要があります。これにより、テストや保守、読み取りが容易になります。私は時間をかけてこれを行いました:

public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken) {

    final WrapperClass unpackedWrapper = unpackWrapper(wrapper);
    boolean wrapperValid = isUnpackedWrapperValid(unpackedWrapper);

    Key key = null;
    try {
        key = unpackedWrapper.getKey();
    } catch (final Exception exception) {
        return wrapperValid;
    }

    if (key != null) {   
        if (doesKeyMeetsBasicConditions(key)) {
            return wrapperValid;
        }
        if (doesKeyMeetsValueConditions(key)) {
            return true;
        }
    }
    return wrapperValid;
}

protected static WrapperClass unpackWrapper(final WrapperClass wrapper) {      
    if (wrapper != null && wrapper.length() > 7 && wrapper.substring(0, 6).equalsIgnoreCase("XYZ")) {
        return wrapper.substring(7, wrapper.lastIndexOf('.') + 1);
    }
    return wrapper;
}

protected static boolean isUnpackedWrapperValid(final WrapperClass wrapper) {
   return wrapper != null && wrapper.equalsIgnoreCase("TFR");
}

protected static boolean doesKeyMeetsBasicConditions(final Key key) {
    Date tokenExpiryTime = key.getExpiresAt();
    if (tokenExpiryTime != null) {
        return true;
    }

    String algorithm = key.getAlgorithm();
    if (!DESIRED_ALGO.equals(algorithm)) {
        return true;
    }

    String value6 = key.getType();
    if (!DESIRED_TYPE.equals(value6)) {
        return true;
    }
    return false;
}

protected static boolean doesKeyMeetsValueConditions(final Key key) {
    return key.getValue1() != null && key.getValue2().size() > 0
           && key.getValue3() != null && key.getValue4() != null
           && key.getValue5() != null;
}

ドメインロジックがわからないので、一部のメソッドには愚かな名前などがあります。ご覧のとおり、ブランチが少ない(if条件)より小さなメソッドがたくさんあります。テストが簡単です(静的コードはナイスではありませんが、たとえば PowerMock )を使用してモックできます。

4
agabrys

まず第一に、ソナーはあなたにもっと多くのフラグを与えるはずです:wrapperパラメーターを再利用することは通常悪い習慣です、NPEは_wrapper.getKey_を呼び出すのでwrapperはnullになる可能性がありますが、とにかくポイントではありません...

ローカルブール変数を作成して、ifステートメントの数を減らしてみてください(テスト数が5または6未満の場合は1つの大きなifステートメントを使用しますが、多くの場合読みにくくなります)。完了したら、これらのブール変数をテストする1つのブロックと、上の例のように1つのreturnステートメントだけが必要です(必ずしも正確ではありません!)。

_boolean expired = tokenExpiryTime != null;
boolean desiredAlgo = DESIRED_ALGO.equals(key.getAlgorithm());
boolean desiredType = DESIRED_TYPE.equals(value6);
if (expired || !desiredAlgo || !desiredType) {
    return isValidWrapper;
}
_

ただし、この種のアルゴリズムがトリガーする場合、認知複雑度レベルはかなり低く見えます...

アルゴリズムの複雑さを軽減するもう1つの大きな方法は、コードのサブブロック(ループ、ifおよびtry-catch)をプライベートメソッドに変換することです。あなたの例では、checkWrapperValidityメソッドのようなもので、isValidWrapperを返すすべてのテストを担当します

0
HBo

少し書き直したことで簡素化されましたが、それでも改善の余地があります。

public static boolean isWrapperValid(WrapperClass wrapper, boolean isTechnicalToken){
    if (wrapper != null && wrapper.length() > 7
        && wrapper.substring(0, 6).equalsIgnoreCase("XYZ"))
    {
        wrapper = wrapper.substring(7, wrapper.lastIndexOf('.')+1);
    }
    boolean isValidWrapper = wrapper != null && wrapper.equalsIgnoreCase("TFR");

    try {
        String key = wrapper.getKey();
        if (key != null && key.getExpiresAt() == null
                && DESIRED_ALGO.equals(key.getAlgorithm())
                && DESIRED_TYPE.equals(key.getType())
                && key.getValue1() != null && !key.getValue2().isEmpty()
                && key.getValue3() != null && key.getValue4() != null
                && key.getValue5() != null) {
            isValidWrapper = true;
        }
    }
    catch (Exception exception) {
        // DO NOTHING
    }
    return isValidWrapper;
}

コメント後:ここですべての呼び出しの例外をキャッチします。

0
Joop Eggen