【问题标题】:SonarQube Refactor this method to reduce its Cognitive ComplexitySonarQube 重构此方法以降低其认知复杂性
【发布时间】:2018-09-19 23:56:33
【问题描述】:

我有以下实用程序方法,并且我正在使用多个 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;
}

请分享您对重构此代码的建议。

【问题讨论】:

  • (wrapper != null && wrapper.length() > 7 && wrapper.substring(0, 6).equalsIgnoreCase("XYZ")) == false。除非“XYZ”实际上不是“XYZ”。
  • 这永远不会是真的wrapper.substring(0, 6).equalsIgnoreCase("XYZ")。因为您创建了一个长度为六个字符的子字符串 (wrapper.substring(0, 6))。因此它永远不能等于XYZ
  • @SubOptimal 这正是我写上面评论时的想法。尽管如此,它还是有可能的,因为我们并不确切知道WrapperClass 是什么。可能WrapperClass.substring() 并没有像我们认为的那样做!
  • @DodgyCodeException 好点。没想到。
  • @DodgyCodeException "XYZ" 我一直只是不显示原始值.. 请忽略这些东西

标签: java sonarqube


【解决方案1】:

首先,Sonar 应该给你更多的标志:重用wrapper 参数通常是一个不好的做法,NPE 调用wrapper.getKey 因为wrapper 可以为空,但无论如何,这不是重点......

尝试通过创建局部布尔变量来减少 if 语句的数量(如果您的测试少于 5 或​​ 6 个,则可能是 1 个大的 if 语句,但通常可读性较差)。完成后,您应该只有 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;
}

但是,如果这种算法触发它,你的认知复杂度水平似乎很低......

另一个降低算法复杂度的重要方法是将子代码块(循环、if 和 try-catch)转换为私有方法。在您的示例中,它可能类似于 checkWrapperValidity 方法,负责每个测试返回 isValidWrapper

【讨论】:

    【解决方案2】:

    稍作改写带来了简化,但仍有待改进。

    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;
    }
    

    评论后:我在这里捕获所有调用的任何异常。

    【讨论】:

    • 它不是完全等效的(但可能还可以)。如果key = wrapper.getKey(); 之后调用的任何方法引发异常,原始版本将引发异常。您的代码将正常返回。
    • @DodgyCodeException 你是对的;应该加注。几乎只有 getter,奇怪的 isValidWrapper 用法让我的注意力误入歧途。
    【解决方案3】:

    我不认为将许多if 条件合并为一个或简单地进行代码清理(例如通过更改某些指令的顺序)可以解决您的问题。

    您的代码与single responsibility principle 不匹配。您应该将这个大方法重构为更小的部分。因此,它将可测试,更易于维护和阅读。我花了一些时间做了这个:

    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();
        return !DESIRED_TYPE.equals(value6);
    }
    
    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 来模拟它。

    【讨论】:

    • 关于单一责任原则的要点。但请注意,它不仅适用于方法,也适用于类。看起来WrapperClassStringMap 之间的某种可怕的交叉。但是也许 OP 无法访问该类并且无法更改它。
    • 是的,我同意。我的代码只是“如何开始”的一个例子。当您不知道域甚至示例代码都无法编译时,很难重构某些东西(它已被审查:P - String 类没有 getExpiresAt 方法)。
    • 好收获!也许 OP 的 String 不是 java.lang.String 而是他自己的带有 import my.package.String; 声明的类。
    • @agabrys 谢谢.. 这是一个非常好的方法.. 一旦有机会,我将在此代码上运行声纳 qube 并检查问题是否消失
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-08
    • 2021-10-31
    • 1970-01-01
    • 2021-06-29
    • 2019-11-02
    • 2021-09-22
    • 1970-01-01
    相关资源
    最近更新 更多