Не уверен, как поступить с этим методом, чтобы уменьшить цикломатическую сложность. Сонар сообщает 13, тогда как 10 ожидается. Я уверен, что нет ничего плохого в том, чтобы оставить этот метод, поскольку он просто бросает мне вызов, как поступить в соответствии с правилом Сонара. Любые мысли будут с благодарностью.
public static long parseTimeValue(String sValue) {
if (sValue == null) {
return 0;
}
try {
long millis;
if (sValue.endsWith("S")) {
millis = new ExtractSecond(sValue).invoke();
} else if (sValue.endsWith("ms")) {
millis = new ExtractMillisecond(sValue).invoke();
} else if (sValue.endsWith("s")) {
millis = new ExtractInSecond(sValue).invoke();
} else if (sValue.endsWith("m")) {
millis = new ExtractInMinute(sValue).invoke();
} else if (sValue.endsWith("H") || sValue.endsWith("h")) {
millis = new ExtractHour(sValue).invoke();
} else if (sValue.endsWith("d")) {
millis = new ExtractDay(sValue).invoke();
} else if (sValue.endsWith("w")) {
millis = new ExtractWeek(sValue).invoke();
} else {
millis = Long.parseLong(sValue);
}
return millis;
} catch (NumberFormatException e) {
LOGGER.warn("Number format exception", e);
}
return 0;
}
Все методы ExtractXXX определены как static
внутренние классы. Например, как один ниже -
private static class ExtractHour {
private String sValue;
public ExtractHour(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
return millis;
}
}
ОБНОВЛЕНИЕ 1
Я собираюсь успокоиться со смесью предложений здесь, чтобы удовлетворить парня Сонара. Определенно место для улучшений и упрощений.
Гуава Function
- просто нежелательная церемония здесь. Хотел обновить вопрос о текущем статусе. Здесь нет ничего окончательного. Пожалуйста, поделитесь своими мыслями
public class DurationParse {
private static final Logger LOGGER = LoggerFactory.getLogger(DurationParse.class);
private static final Map<String, Function<String, Long>> MULTIPLIERS;
private static final Pattern STRING_REGEX = Pattern.compile("^(\\d+)\\s*(\\w+)");
static {
MULTIPLIERS = new HashMap<>(7);
MULTIPLIERS.put("S", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractSecond(input).invoke();
}
});
MULTIPLIERS.put("s", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractInSecond(input).invoke();
}
});
MULTIPLIERS.put("ms", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractMillisecond(input).invoke();
}
});
MULTIPLIERS.put("m", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractInMinute(input).invoke();
}
});
MULTIPLIERS.put("H", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractHour(input).invoke();
}
});
MULTIPLIERS.put("d", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractDay(input).invoke();
}
});
MULTIPLIERS.put("w", new Function<String, Long>() {
@Nullable
@Override
public Long apply(@Nullable String input) {
return new ExtractWeek(input).invoke();
}
});
}
public static long parseTimeValue(String sValue) {
if (isNullOrEmpty(sValue)) {
return 0;
}
Matcher matcher = STRING_REGEX.matcher(sValue.trim());
if (!matcher.matches()) {
LOGGER.warn(String.format("%s is invalid duration, assuming 0ms", sValue));
return 0;
}
if (MULTIPLIERS.get(matcher.group(2)) == null) {
LOGGER.warn(String.format("%s is invalid configuration, assuming 0ms", sValue));
return 0;
}
return MULTIPLIERS.get(matcher.group(2)).apply(matcher.group(1));
}
private static class ExtractSecond {
private String sValue;
public ExtractSecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = Long.parseLong(sValue);
return millis;
}
}
private static class ExtractMillisecond {
private String sValue;
public ExtractMillisecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue));
return millis;
}
}
private static class ExtractInSecond {
private String sValue;
public ExtractInSecond(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 1000);
return millis;
}
}
private static class ExtractInMinute {
private String sValue;
public ExtractInMinute(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 60 * 1000);
return millis;
}
}
private static class ExtractHour {
private String sValue;
public ExtractHour(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 60 * 60 * 1000);
return millis;
}
}
private static class ExtractDay {
private String sValue;
public ExtractDay(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 24 * 60 * 60 * 1000);
return millis;
}
}
private static class ExtractWeek {
private String sValue;
public ExtractWeek(String sValue) {
this.sValue = sValue;
}
public long invoke() {
long millis;
millis = (long) (Double.parseDouble(sValue) * 7 * 24 * 60 * 60 * 1000);
return millis;
}
}
}
ОБНОВЛЕНИЕ 2
Хотя я добавил свое обновление, оно того стоит. Я собираюсь идти дальше, так как Сонар сейчас не жалуется. Не волнуйтесь, я принимаю ответ mattnz, так как это путь, и я не хочу показывать плохой пример тем, кто сталкивается с этим вопросом. Итог - Не переусердствуйте, потому что Сонар (или руководитель проекта Half Baked) жалуется на CC. Просто сделайте то, что стоит копейки для проекта. Спасибо всем.
private static Dictionary<string,Func<string,long>> _mappingStringToParser;
я оставлю все остальное как упражнение для вас (или человека, у которого сейчас больше свободного времени, чем у меня). Если вы знакомы с монадическими парсерами, вы можете найти более чистый API, но я не пойду туда прямо сейчасExtractBlah
определяются классы? это из какой-то библиотеки или доморощенного?Ответы:
Ответ разработки программного обеспечения:
Это всего лишь один из многих случаев, когда простой подсчет бинов, которые просто подсчитать, заставит вас поступить неправильно. Это не сложная функция, не меняйте ее. Цикломатическая сложность - это всего лишь руководство к сложности, и вы плохо ее используете, если изменяете эту функцию на ее основе. Он прост, удобочитаем, удобен в обслуживании (на данный момент), если в будущем он станет больше, СС будет расти в геометрической прогрессии и получит то внимание, которое ему нужно, когда это нужно, а не раньше.
Миньон работает в крупной международной корпорации Ответ:
Организации полны переплаченных, непродуктивных команд счетчиков бобов. Удовлетворять счетчики бобов легче и, конечно, мудрее, чем делать правильные вещи. Вам нужно изменить порядок действий, чтобы снизить CC до 10, но будьте честны с тем, почему вы это делаете - чтобы не было счетчиков бобов за спиной. Как предлагается в комментариях - «монадные парсеры» могут помочь
источник
Спасибо @JimmyHoffa, @MichaelT и @ GlenH7 за помощь!
питон
Перво-наперво, вы действительно должны принимать только известные префиксы, то есть «H» или «H». Если вам нужно принять и то и другое, вам следует выполнить некоторую операцию, чтобы согласованно сохранить место на вашей карте.
В Python вы можете создать словарь.
Тогда мы хотим, чтобы метод использовал это:
Должен иметь лучшую цикломатическую сложность.
Джава
Нам нужен только 1 (один) каждого множителя. Давайте поместим их в карту, как предлагают некоторые другие ответы.
Тогда мы можем просто использовать карту, чтобы получить правильный конвертер
источник
Так как вы
return millis
в конце этого ужасного ifelseifelse в любом случае, первое, что приходит на ум, это немедленно вернуть значение изнутри блоков if. Этот подход следует за тем, который указан в каталоге шаблонов рефакторинга как Заменить Вложенное Условное с Охранными предложениями .Это поможет вам избавиться от чужих, сгладить код и сделать сонар счастливым:
Еще одна вещь, которую стоит рассмотреть, это сбросить блок try-catch. Это также снизит цикломатическую сложность, но главная причина, по которой я рекомендую это, состоит в том, что в этом блоке нет никакого способа для кода вызывающего абонента отличить юридически проанализированный 0 от исключения числового формата.
Если вы не уверены на 200%, что возвращение 0 для ошибок синтаксического анализа - это то, что нужно коду вызывающей стороны, вам лучше распространить это исключение и позволить коду вызывающей стороны решить, как с ним работать. Как правило, в вызывающей программе более удобно решить, прервать ли выполнение или повторить попытку получения ввода, или вернуться к какому-либо значению по умолчанию, например 0 или -1, или как угодно.
Ваш фрагмент кода для примера ExtractHour заставляет меня чувствовать, что функциональность ExtractXXX разработана далеко не оптимально. Бьюсь об заклад, каждый из оставшихся классов бездумно повторяет одно
parseDouble
и то жеsubstring
, и умножает такие вещи, как 60 и 1000 снова и снова и снова.Это потому, что вы упустили суть того, что нужно сделать, в зависимости от того, что
sValue
именно: он определяет, сколько символов нужно вырезать из конца строки и каково будет значение множителя. Если вы спроектируете свой «основной» объект на основе этих важных функций, он будет выглядеть следующим образом:После этого вам понадобится код, который настраивает вышеуказанные объекты для конкретного условия, если оно выполнено, или как-то «обходит» иное. Это можно сделать следующим образом:
Исходя из описанных выше строительных блоков , код вашего метода может выглядеть следующим образом:
Видите ли, в методе не осталось никаких сложностей, никаких фигурных скобок (ни многократных возвратов, как в моем первоначальном предложении грубой силы при выравнивании кода). Вы просто последовательно проверяете ввод и корректируете обработку по мере необходимости.
источник
Если вы действительно хотите провести рефакторинг, вы можете сделать что-то вроде этого:
Идея состоит в том, что у вас есть карта ключей (которую вы постоянно используете в "setswith"), которые сопоставляются с конкретными объектами, которые выполняют необходимую обработку.
Здесь немного грубо, но я надеюсь, что это достаточно ясно. Я не заполнил детали,
extractKeyFromSValue()
потому что я просто недостаточно знаю, что это за строки, чтобы сделать это правильно. Похоже, что это последние 1 или 2 нечисловых символа (регулярное выражение, вероятно, может извлечь его достаточно легко, возможно,.*([a-zA-Z]{1,2})$
будет работать), но я не уверен на 100% ...Оригинальный ответ:
Вы могли бы изменить
в
Это может немного вас спасти, но, честно говоря, я бы не слишком волновался об этом. Я согласен с вами, что я не думаю, что оставить метод таким, какой он есть, приносит много вреда. Вместо того, чтобы пытаться «подчиняться правилам Сонара», старайтесь «держаться как можно ближе к правилам Сонара».
Вы можете свести себя с ума, пытаясь следовать каждому отдельному правилу, которое будут иметь эти инструменты анализа, но вы также должны решить, имеют ли правила смысл для вашего проекта, и для конкретных случаев, когда время, потраченное на рефакторинг, может не стоить того ,
источник
Вы можете рассмотреть возможность использования enum для хранения всех доступных вариантов и предикатов для сопоставления значений. Как упоминалось ранее, ваша функция достаточно читабельна, чтобы оставить ее неизменной. Эти показатели помогут вам, а не наоборот.
источник
Связанный с вашим комментарием:
Другой вариант - изменить стандарты кодирования вашей команды для подобных ситуаций. Возможно, вы можете добавить какое-то групповое голосование, чтобы обеспечить меру управления и избежать кратковременных ситуаций.
Но изменение стандартов команды в ответ на ситуации, которые не имеют смысла, является признаком хорошей команды с правильным отношением к стандартам. Стандарты призваны помочь команде, а не мешать написанию кода.
источник
Честно говоря, все технические ответы, представленные выше, кажутся ужасно сложными для поставленной задачи. Как уже было написано, сам код чист и хорош, поэтому я бы выбрал наименьшее возможное изменение, чтобы удовлетворить счетчик сложности. Как насчет следующего рефакторинга:
Если я считаю правильно, извлеченная функция должна иметь сложность 9, которая все еще соответствует требованиям. И это в основном тот же код, что и раньше , и это хорошо, так как код был хорош для начала.
Кроме того, читателям Чистого кода может понравиться тот факт, что метод верхнего уровня теперь прост и короток, а извлеченный - с деталями.
источник