Рефакторинг метода, имеющего несколько операторов переключения и дженериков.

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

Мне нужно провести рефакторинг этого метода, так как в ближайшее время появится требование, которое приведет к тому, что в этом методе будет введено большее количество случаев переключения.

Я попробовал другие возможные решения, показанные в других вопросах о переполнении стека, но эти решения не применимы для моего варианта использования.

Фрагмент кода упомянут ниже.

      protected static < T extends Comparable < T >> boolean compareValues(T lookupValue, T actualValue, String comparisonCondition, List < T > lookupValues) {
    comparisonCondition = comparisonCondition.toUpperCase();
    boolean result;
    switch (comparisonCondition) {
        case EQUALS:
            result = lookupValue instanceof String && actualValue instanceof String ? (String.valueOf(lookupValue).trim()).equalsIgnoreCase(String.valueOf(actualValue).trim()) : lookupValue.compareTo(actualValue) == 0;
            break;
        case NOT_EQUALS:
            result = lookupValue.compareTo(actualValue) != 0;
            break;
        case LIKE:
            result = StringUtils.containsIgnoreCase(String.valueOf(actualValue), String.valueOf(lookupValue));
            break;
        case NOT_LIKE:
            result = !StringUtils.containsIgnoreCase(String.valueOf(actualValue), String.valueOf(lookupValue));
            break;
        case IN:
            result = lookupValues.stream().anyMatch(lkpValue - > lkpValue instanceof String ? ((String) lkpValue).trim().compareToIgnoreCase(String.valueOf(actualValue).trim()) == 0 : lkpValue.compareTo(actualValue) == 0);
            break;
        case NOT_IN:
            result = lookupValues.stream().noneMatch(lkpValue - > lkpValue instanceof String ? ((String) lkpValue).trim().compareToIgnoreCase(String.valueOf(actualValue).trim()) == 0 : lkpValue.compareTo(actualValue) == 0);
            break;
        default:
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(MSG_FORMAT_INVALID_COMPARISON_CONDITION, comparisonCondition);
            }
            result = false;
    }
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Comparing value '{}' with '{}' using comparison condition '{}'.{}Result: {}", actualValue, Objects.nonNull(lookupValue) ? lookupValue : lookupValues.stream().map(Object::toString).collect(Collectors.joining(WhlProcessingConstants.SPLIT_COMMA)), comparisonCondition, LINE_SEPARATOR, result);
    }
    return result;
}

Можете ли вы предложить какое-нибудь решение, с помощью которого этот код метода можно будет реорганизовать, чтобы его можно было масштабировать для любых будущих требований, а также чтобы когнитивная сложность не увеличивалась, поскольку мы включаем в него больше условий/сценариев сравнения?

Для информации: я использую плагин SonarLint для анализа когнитивной сложности кода.

2 ответа

что вы думаете об определении карты со всеми этими предикатами и ключами? Кроме того, вы можете попробовать извлечь общие сравнения, которые постоянно повторяются. Вот возможное решение, но вам, возможно, придется изменить несколько вещей:

      private static final Map<String, TriPredicate<T>> comparators = Map.of(
        "EQUALS", (lookup, actual, __) -> lookup instanceof String && actual instanceof String ? compareStrings(lookup, actual) : lookup.compareTo(actual) == 0,
        "NOT_EQUALS", (lookup, actual, __) -> lookup.compareTo(actual) != 0,
        "LIKE", (lookup, actual, __) -> containsIgnoreCase(lookup, actual),
        "NOT_LIKE", (lookup, actual, __) -> !containsIgnoreCase(lookup, actual),
        "IN", (__, actual, lookup) -> listContains(actual, lookup),
        "NOT_IN", (__, actual, lookup) -> !listContains(actual, lookup)
);

protected static <T extends Comparable<T>> boolean compareValues(T lookupValue, T actualValue, String comparisonCondition, List<T> lookupValues) {
    if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Comparing value '{}' with '{}' using comparison condition '{}'.{}Result: {}", actualValue, Objects.nonNull(lookupValue) ? lookupValue : lookupValues.stream().map(Object::toString).collect(Collectors.joining(WhlProcessingConstants.SPLIT_COMMA)), comparisonCondition, LINE_SEPARATOR, result);
    }
    return comparators.getOrDefault(comparisonCondition.toUpperCase(), (a, b, c) -> false)
            .test(lookupValue, actualValue, lookupValues);
}

private <T extends Comparable> boolean listContains(T actual, List<T> lookup) {
    return lookup.stream().anyMatch(val -> val instanceof String ? compareStrings(val, actual) : val.compareTo(actual) == 0);
}

private boolean containsIgnoreCase(Object actual, Object lookup) {
    return StringUtils.containsIgnoreCase(String.valueOf(actual), String.valueOf(lookup));
}

private boolean compareStrings(Object lookup, Object actual) {
    return (String.valueOf(lookup).trim()).equalsIgnoreCase(String.valueOf(actual).trim());
}


@FunctionalInterface
interface TriPredicate<X> {
    boolean test(X lookup, X actual, List<X> lookupValues);
}

Очень похожий пример из книги «Эффективная Java» и после добавления дженериков будет работать.

      interface Operation {
    public <lookup,actual> boolean validate(lookup lookupValue, actual actualValue);
}
 enum BasicOperationn implements Operation{
    EQUALS{
        @Override
        public <lookup, actual> boolean validate(lookup lookupValue, actual actualValue) {
          return lookupValue instanceof String && actualValue instanceof String ? (String.valueOf(lookupValue).trim()).equalsIgnoreCase(String.valueOf(actualValue).trim()) :((String) lookupValue).compareTo(((String) actualValue)) == 0;        }
    },
     LIKE {
         @Override
         public <lookup, actual> boolean validate(lookup lookupValue, actual actualValue) {
           return   StringUtils.containsIgnoreCase(String.valueOf(actualValue), String.valueOf(lookupValue));
         }
     };
     ...

}

Отредактировано: добавление клиентского кода.

      class ClientClass{
    protected static < T extends Comparable < T >> boolean compareValues(T lookupValue, T actualValue, String comparisonCondition, List< T > lookupValues) {
        comparisonCondition = comparisonCondition.toUpperCase();
        BasicOperationn operation = BasicOperationn.valueOf(comparisonCondition);
        boolean result = operation.validate(lookupValue, actualValue);
        return result;
    }
}
Другие вопросы по тегам