Уменьшение цикломатической сложности оператора Switch — Sonar

Я хочу уменьшить цикломатическую сложность моего переключателя, мой код:

public String getCalenderName() {
        switch (type) {
    case COUNTRY:
        return country == null ? name : country.getName() + HOLIDAY_CALENDAR;
    case CCP:
        return ccp == null ? name : ccp.getName() + " CCP" + HOLIDAY_CALENDAR;
    case EXCHANGE:
        return exchange == null ? name : exchange.getName() + HOLIDAY_CALENDAR;
    case TENANT:
        return tenant == null ? name : tenant.getName() + HOLIDAY_CALENDAR;
    default:
        return name;
    }
}

Сложность этих кодовых блоков составляет 16, и я хочу уменьшить ее до 10. Страна, ccp, биржа и арендатор — мои разные объекты. В зависимости от типа я буду называть соответствующий метод.


person Amar Magar    schedule 15.11.2016    source источник
comment
сложность этого кода составляет 16, и вы хотите уменьшить его до 10. Почему бы не уменьшить его до 9? или 8? Или 11? Почему 16 проблематично?   -  person Andy Turner    schedule 15.11.2016
comment
Согласно моим правилам сонара, я хочу, чтобы оно было ниже 10, будет здорово, если мы сможем уменьшить его еще больше. @Энди Тернер   -  person Amar Magar    schedule 15.11.2016
comment
@AmarMagar, вы забыли добавить операторы break в каждом случае или это сделано намеренно? Я не уверен, поможет ли добавление операторов break уменьшить цикломатическую сложность.   -  person kk.    schedule 15.11.2016
comment
@Krishna Kuntala Нет необходимости в утверждении break, поскольку в каждом случае он что-то возвращает.   -  person Andrei Olar    schedule 15.11.2016


Ответы (7)


Я считаю, что это Sonar предупреждение. Я думаю, что Sonar предупреждения — это не обязательные правила, а просто рекомендации. Ваш блок кода READABLE и MAINTAINABLE как есть. Это уже просто, но если вы действительно хотите изменить это, вы можете попробовать эти два подхода ниже и посмотреть, станет ли сложность ниже:

Примечание: сейчас у меня нет с собой компилятора, поэтому могут быть ошибки, заранее извините.

Первый подход:

Map<String, String> multipliers = new HashMap<String, Float>();
    map.put("country", country);
    map.put("exchange", exchange);
    map.put("ccp", ccp);
    map.put("tenant", tenant);

Затем мы можем просто использовать карту, чтобы получить нужный элемент.

    return map.get(type) == null ? name : map.get(type).getName() + HOLIDAY_CALENDAR;

2-й подход:

Все ваши объекты имеют один и тот же метод, поэтому вы можете добавить в него метод Interface с getName() и изменить подпись метода, например:

getCalendarName(YourInterface yourObject){
    return yourObject == null ? name : yourObject.getName() + HOLIDAY_CALENDAR;
}
person halil    schedule 15.11.2016
comment
уже попробовал первый подход, но не смог вызвать getName для значения карты - person Amar Magar; 15.11.2016
comment
Затем используйте второй подход, введите в нем новый интерфейс с методом getName(). Вы должны использовать преимущества полиморфизма, если у вас есть общее поведение/метод. - person halil; 15.11.2016

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

 public String getCalenderName() {
    switch (type) {
    case COUNTRY:
        return getCountryName();
    case CCP:
        return getCcpName();
    case EXCHANGE:
        return getExchangeName();
    case TENANT:
        return getTenantName();
    default:
        return name;
    }
}

private String getCountryName() {
    return country == null ? name : country.getName() + HOLIDAY_CALENDAR;
}

private String getCcpName() {
    return ccp == null ? name : ccp.getName() + " CCP" + HOLIDAY_CALENDAR;
}

private String getExchangeName() {
    return exchange == null ? name : getName.toString() + HOLIDAY_CALENDAR;
}

private String getTenantName() {
    return tenant == null ? name : getName.toString() + HOLIDAY_CALENDAR;
}

Обратите внимание, что в вашем конкретном примере я полагаю, что у вас есть 1 класс, который собирает (по крайней мере) 4 очень похожих поведения. Рефакторинг, безусловно, имел бы больше смысла, чтобы иметь, например, одну базовую реализацию (абстрактную или нет) и 4 других унаследованных класса.

person Loic Mouchard    schedule 15.11.2016
comment
Конечно, добавьте javadoc, если вы не хотите, чтобы сонар жаловался;) - person Loic Mouchard; 15.11.2016

Насколько мне известно, не используйте оператор return в операторе switch. Примените эту логику после оператора switch, используя переменную. Создайте метод для проверки нулевого значения и вызовите этот метод из переключателя, после чего вы сможете уменьшить цикломатическую сложность.

person mayank agrawal    schedule 15.11.2016

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

Возьмем кейс:

case COUNTRY:
  return country == null ? name : country.getName() + HOLIDAY_CALENDAR;

Это означает, что если тип календаря COUNTRY, страна, с которой связан календарь, может быть null. Это то, что вы должны предотвратить по дизайну, потому что я не вижу причины, по которой это может быть допустимой ситуацией. Зачем вам страновой календарь без страны?

Поэтому убедитесь, что с календарем связан ненулевой объект, как только вы назначите ему type. Таким образом, ваши случаи будут похожи на

case COUNTRY:
  return country.getName() + HOLIDAY_CALENDAR;

что снижает вашу цикломатическую сложность до 5.

person Paul Jansen    schedule 15.07.2018

Вы можете удалить все нулевые сравнения и проверить их перед переключением регистра. В этом случае сложность уменьшится на 4 или может быть больше.

person sandeshch    schedule 15.11.2016

Если ваши объекты: страна, cpp, биржа и арендатор используют один и тот же интерфейс, например. ObjectWithGetName, вы можете реорганизовать свой код следующим образом:

  public String getCalenderName() {
    ObjectWithGetNameMethod calendarType = null;
    switch (type) {
        case COUNTRY:
           calendarType = country;
           break;
        case CCP:
           calendarType = cpp;
           break;
        case EXCHANGE:
           calendarType = exchange;
           break;
        case TENANT:
           calendarType = tenant;
           break;
        default:
           calendarType = null;
    }
    return (calendarType != null ? (calendarType.getName() + HOLIDAY_CALENDAR) : name);
  }

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

person Luk    schedule 15.11.2016
comment
это решение не работает, потому что страна, ccp, биржа и арендатор — разные сущности. поэтому во время вызова метода getName я должен использовать соответствующий объект для вызова метода. - person Amar Magar; 16.11.2016

public String getName() {
    if (type == null) {
        return name;
    }
    if (type == BusinessCalendarType.COUNTRY) {
        return country == null ? name : country.getName() + HOLIDAY_CALENDAR;
    } else if (type == BusinessCalendarType.CCP) {
        return ccp == null ? name : ccp.getName() + " CCP" + HOLIDAY_CALENDAR;
    } else if (type == BusinessCalendarType.EXCHANGE) {
        return exchange == null ? name : exchange.getName() + HOLIDAY_CALENDAR;
    } else if (type == BusinessCalendarType.TENANT)  {
        return tenant == null ? name : tenant.getName() + HOLIDAY_CALENDAR;
    } else {
        return name;
    }
}

это сработало для меня

person Amar Magar    schedule 15.11.2016
comment
Это функционально неправильно. Значение по умолчанию из оператора swicht не соответствует type==null - person Loic Mouchard; 15.11.2016
comment
на самом деле type является Enum и обязательно вернет одно из этих 4 значений или только null. В моем вопросе я написал заявление по умолчанию из-за предупреждения сонара. В любом случае ты прав. Я изменил этот ответ в соответствии с вашим предложением. - person Amar Magar; 16.11.2016
comment
Вы только что сделали относительно (человечески) удобочитаемую функцию, которую гораздо труднее прочитать, чтобы соответствовать правилу предупреждения в сонаре... - person Johan Aspeling; 14.08.2019