Как улучшить цикломатическую сложность? - PullRequest
11 голосов
/ 17 декабря 2010

Цикломатическая сложность будет высокой для методов с большим количеством утверждений, в том числе для операторов if / while /.Так как мы можем улучшить это?

Я занимаюсь большим проектом, где я должен уменьшить CC для методов с CC> 10. И есть много методов с этой проблемой.Ниже я перечислю некоторые примеры шаблонов кода (не фактический код) с проблемами, с которыми я столкнулся.Возможно ли, что их можно упростить?

Пример дел, приводящих к множеству утверждений о решениях:

Дело 1)

if(objectA != null) //objectA is a pass in as a parameter
{
 objectB = doThisMethod();
 if(objectB != null)
 {
  objectC = doThatMethod();
  if(objectC != null)
  {
   doXXX();
  }
  else{
   doYYY();
  }
 }
 else
 {
  doZZZ();
 }
}

Дело 2)

if(a < min)
 min = a;

if(a < max)
 max = a;

if(b > 0)
 doXXX();

if(c > 0)
{
 doYYY();
}
else
{
 doZZZ();
 if(c > d)
  isTrue = false;

 for(int i=0; i<d; i++)
  s[i] = i*d;

 if(isTrue)
 {
  if(e > 1)
  {
   doALotOfStuff();
  }
 }
}

Дело 3)

// note that these String Constants are used elsewhere as diff combination,
// so you can't combine them as one
if(e.PropertyName.Equals(StringConstants.AAA) ||
e.PropertyName.Equals(StringConstants.BBB) ||
e.PropertyName.Equals(StringConstants.CCC) ||
e.PropertyName.Equals(StringConstants.DDD) ||
e.PropertyName.Equals(StringConstants.EEE) ||
e.PropertyName.Equals(StringConstants.FFF) ||
e.PropertyName.Equals(StringConstants.GGG) ||
e.PropertyName.Equals(StringConstants.HHH) ||
e.PropertyName.Equals(StringConstants.III) ||
e.PropertyName.Equals(StringConstants.JJJ) ||
e.PropertyName.Equals(StringConstants.KKK)) 
{
 doStuff();
}

Ответы [ 4 ]

13 голосов
/ 17 декабря 2010

Случай 1 - справиться с этим просто путем рефакторинга в меньшие функции. Например. следующий фрагмент может быть функцией:

objectC = doThatMethod();
if(objectC != null)
{
 doXXX();
}
else{
 doYYY();
}

Случай 2 - точно такой же подход. Вынесите содержимое предложения else в вспомогательную функцию меньшего размера

Случай 3 - составить список строк, с которыми вы хотите проверить, и создать небольшую вспомогательную функцию, которая сравнивает строку со многими параметрами (может быть дополнительно упрощена с помощью linq)

var stringConstants = new string[] { StringConstants.AAA, StringConstants.BBB etc };
if(stringConstants.Any((s) => e.PropertyName.Equals(s))
{
    ...
}
9 голосов
/ 17 декабря 2010

Вы должны использовать рефакторинг Заменить Условное на Полиморфизм , чтобы уменьшить CC.

Разница между условным полиморфным кодом заключается в том, что в полиморфном коде решение принимается во время выполнения. Это дает вам больше гибкости для добавления \ изменения \ удаления условий без изменения кода. Вы можете проверить поведение отдельно, используя модульные тесты, которые улучшают тестируемость. Кроме того, поскольку будет меньше условного кода, это означает, что код легко читается, а CC меньше.

Для более детального изучения шаблонов поведенческого дизайна esp. Стратегия .

Я бы сделал первый случай, подобный этому, чтобы удалить условные обозначения и, следовательно, CC. Более того, код более объектно-ориентирован, читаем и тестируем.

void Main() {
    var objectA = GetObjectA();
    objectA.DoMyTask();
}

GetObjectA(){
    return If_All_Is_Well ? new ObjectA() : new EmptyObjectA();
}

class ObjectA() {
    DoMyTask() {
        var objectB = GetObjectB();
        var objectC = GetObjectC();
        objectC.DoAnotherTask();     // I am assuming that you would call the doXXX or doYYY methods on objectB or C because otherwise there is no need to create them
    }

    void GetObjectC() {
        return If_All_Is_Well_Again ? new ObjectC() : new EmptyObjectC();
    }
}

class EmptyObjectA() { // http://en.wikipedia.org/wiki/Null_Object_pattern
    DoMyTask() {
        doZZZZ();
    }
}

class ObjectC() {
    DoAnotherTask() {
        doXXX();
    }
}

class EmptyObjectB() { 
    DoAnotherTask() {
        doYYY();
    }
}

Во втором случае сделать то же самое, что и первый.

В третьем случае -

var myCriteria = GetCriteria();
if(myCriteria.Contains(curretnCase))
    doStuff();

IEnumerable<Names> GetCriteria() {
   // return new list of criteria.
}
2 голосов
/ 17 декабря 2010

Я не программист на C #, но попробую.

В первом случае я бы сказал, что объекты не должны быть нулевыми. Если это неизбежно (обычно этого можно избежать), я бы использовал шаблон раннего возврата:

if ( objectA == NULL ) {
    return;
}
// rest of code here

Второй случай, очевидно, нереалистичный код, но я бы, по крайней мере, сказал бы:

if ( isTrue && e > 1 ) {
    DoStuff();
}

вместо использования двух отдельных if.

И в последнем случае я буду хранить строки для тестирования в массиве / векторе / карте и использовать эти контейнеры для выполнения поиска.

И, наконец, хотя использование цикломатической сложности - это «хорошо» (тм), и я использую его сам, есть некоторые функции, которые, естественно, должны быть немного сложными - проверка пользовательского ввода является примером. Я часто хочу, чтобы инструмент CC, который я использую (Source Monitor at http://www.campwoodsw.com - бесплатно и очень хорошо), поддерживал белый список функций, которые, как я знаю, должны быть сложными и которые я не хочу отмечать.

1 голос
/ 17 декабря 2010

Последнее, если в случае 2 может быть упрощено:

 if(isTrue)
 {
  if(e > 1)
  {

можно заменить на

if(isTrue && (e>1))

случае 3 можетможно переписать так:

new string[]{StringConstants.AAA,...}
    .Contains(e.PropertyName)

Вы можете даже превратить массив строк в HashSet<String>, чтобы получить производительность O (1).

...