Как улучшить Cyclomatic Complexity?

Цикломатическая сложность будет высокой для методов с большим количеством утверждений, в том числе для операторов 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 u 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();
}

Ответ 1

Случай 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))
{
    ...
}

Ответ 2

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

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

Подробнее о поведенческих шаблонах проектирования. Strategy.

Я бы сделал первый случай, подобный этому, чтобы удалить условные обозначения и, следовательно, 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.
}

Ответ 3

Я не программист на С#, но я возьму на него удар.

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

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

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

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

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

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

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

Ответ 4

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

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

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

if(isTrue && (e>1))

case 3 можно переписать как:

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

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