Снижение цикломатикой c сложности - PullRequest
0 голосов
/ 11 февраля 2020

У меня есть класс с более чем 20 + полями одного типа, которые заполняются на разных этапах жизненного цикла объекта.

Один из методов класса должен возвращать значение поля на основе имени поля.

Пока у меня есть что-то вроде этого:

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

проблема в этом - высокая цикломати c сложность.

Какой самый простой способ справиться с этим?

Ответы [ 4 ]

1 голос
/ 11 февраля 2020

Редактировать : Спасибо @ Филиппо Поссенти за его комментарий

Вместо переключателя вы можете использовать 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);
}

Почему бы вам не использовать для этого рефлексию или существующую библиотеку? (Или почему у вас вообще есть такой метод)

0 голосов
/ 11 февраля 2020

Вместо коммутатора или 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);
}

в коде своего клиента.

0 голосов
/ 11 февраля 2020

Предполагая, что fieldName возможные значения соответствуют получателям на bean-компоненте, вы можете использовать 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());
}

Это больше касается улучшения читабельности кода, чем улучшения цикломати c сложности, так что если это чистая производительность, что вы ищете, это не может быть вашим решением.

Если вы ищете чистую производительность, вы можете попробовать использовать лямбды и карту.

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

Отметим, что в приведенном выше примере сложность cyclomati c все еще присутствует, но она перемещена в инициализатор класса 'stati c. Это означает, что вы будете подвергаться скромному наказанию при запуске приложения, но будете получать более высокую производительность при последующих вызовах getFieldValue. Кроме того, если вам нужна производительность, вы можете исключить необходимость toLowerCase ... которую я в моем примере удалил.

0 голосов
/ 11 февраля 2020

Теоретически вы можете уменьшить сложность метода getFieldValue() на:

  • , сохраняя ссылку на метод получения как Producer<?> в Map<String, Producer<?>>
  • , используя отражение для поиска в полях
  • с использованием сторонней библиотеки, которая поддерживает запрос компонента по имени свойства, например commons-beanutils .

Однако каждый из этих подходов увеличит сложность метода getFieldValue() и потенциально снизить производительность. Обе проблемы хуже, чем высокая сложность.

Такое чувство, что вы должны рассмотреть, зачем вам сначала нужен метод getFieldValue(), может быть, это должен быть Map<String, ?>?

...