降低圈复杂度

时间:2020-02-11 10:23:56

标签: java cyclomatic-complexity

我有一个类,在对象生命周期的不同阶段填充了20多个相同类型的字段。

其中一个类方法应根据字段名称返回字段值。

到目前为止,我有这样的事情:

public String getFieldValue(String fieldName){
switch (fieldName.toLowerCase(){
case "id": return getId();
case "name": return getName();
.....

问题是圈复杂度高。

解决这个问题的最简单方法是什么?

4 个答案:

答案 0 :(得分:1)

编辑:感谢 @Filippo Possenti 的评论

您可以使用Map来代替开关。

这里是一个例子。

static interface C {
    String getA();
    String getB();
    String getC();
}

@FunctionalInterface
static interface FieldGetter {
    String get(C c);
}

static Map<String, FieldGetter> fields = Map.of(
        "a", C::getA,
        "b", C::getB,
        "c", C::getC
);

static String getField(C object, String fieldNameToRetrieve) {
    var getter = fields.get(fieldNameToRetrieve);
    if(getter == null) {
        throw new IllegalArgumentException("unknown field");
    }
    return getter.get(object);
}

您为什么不为此使用反射或现有库? (或者为什么你甚至有这种方法)

答案 1 :(得分:0)

理论上,您可以通过以下方式降低getFieldValue()方法的复杂性:

  • Producer<?>中将getter方法引用存储为Map<String, Producer<?>>
  • 使用反射来查找字段
  • 使用支持通过属性名称查询Bean的第3方库。 commons-beanutils

但是,这些方法中的每一种都会增加getFieldValue()方法的复杂性并可能降低性能。两者都是比高复杂性更糟糕的问题。

感觉您应该首先回顾为什么需要getFieldValue()方法,也许应该是Map<String, ?>

答案 2 :(得分:0)

假设fieldName的可能值与Bean上的getter匹配,则可以使用Apache的BeanUtils:

https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/PropertyUtils.html#getSimpleProperty-java.lang.Object-java.lang.String-

基本上,您可以执行以下操作:

public String getFieldValue(String fieldName){
    return PropertyUtils.getSimpleProperty(fieldName.toLowerCase());
}

与提高循环复杂性相比,这更多是关于提高代码的可读性,因此,如果您只是追求纯粹的性能,那么这可能不是您的解决方案。

如果您只追求纯粹的性能,则可以尝试利用lambda和Map。

import java.util.Map;
import java.util.HashMap;
import java.util.function.Function;

public class HelloWorld{
    public static class MyClass {
        private static Map<String, Function<MyClass, Object>> descriptor;

        static {
            descriptor = new HashMap<>();
            descriptor.put("id", MyClass::getId);
            descriptor.put("name", MyClass::getName);
        }

        private String id;
        private String name;

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public void setId(String value) {
            id = value;
        }

        public void setName(String value) {
            name = value;
        }

        public Object getFieldValue(String fieldName) {
            Function fn = descriptor.get(fieldName);
            return fn.apply(this);
        }
    }


    public static void main(String []args){
        MyClass mc = new MyClass();
        mc.setId("hello");
        mc.setName("world");

        System.out.println(mc.getFieldValue("id") + " " + mc.getFieldValue("name"));
    }
}

要注意的是,在上面的示例中,圈复杂性仍然存在,但已在类的静态初始化器中移动了。这意味着您将在应用程序启动期间遭受适度的损失,但是在随后的getFieldValue调用中将获得更高的性能。 另外,如果您想要的是性能,则可能需要消除对toLowerCase ...的需要,在我的示例中,我已将其删除。

答案 3 :(得分:0)

您可以使用Map代替开关或使用enum

enum FieldExtractor implements Function<YourClass, String> {
    ID(YourClass::getId),
    NAME(YourClass::getName); // and so on

    private final Function<YourClass, String> delegate;

    FieldExtractor(Function<YourClass, String> delegate) {
        this.delegate = delegate;
    }
    @Override public String apply(YourClass extractFrom) {
        return delegate.apply(extractFrom);
    }

    static FieldExtractor fromString(String name) {
        return Stream.of(FieldExtractor.values())
                     .filter(fe -> fe.name().equalsIgnoreCase(name))
                     .findFirst()
                     .orElseThrow(IllegalArgumentException::new);
    }
}

现在您可以使用

public String getFieldValue(String fieldName) {
    return FieldExtractor.fromString(fieldName).apply(this);
}

在您的客户代码中。