Рефакторинг метода, имеющего несколько операторов переключения и дженериков.
У меня есть существующий проект, основанный на 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;
}
}