13

对于具有大量决策语句(包括 if/while/for 语句)的方法,循环复杂度会很高。那么我们如何改进它呢?

我正在处理一个大型项目,我应该减少 CC > 10 的方法的 CC。并且有很多方法可以解决这个问题。下面我将列出一些我遇到的问题的代码模式(不是实际代码)。是否可以简化它们?

导致许多决策声明的案例示例:

情况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

4 回答 4

14

案例 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))
{
    ...
}
于 2010-12-17T09:33:37.920 回答
9

您应该使用重构Replace Conditional with Polymorphism来减少 CC。

有条件的多态代码之间的区别在于,在多态代码中,决定是在运行时做出的。这使您可以更灵活地添加\更改\删除条件,而无需修改代码。您可以使用提高可测试性的单元测试单独测试行为。此外,由于条件代码较少意味着代码易于阅读并且CC较少。

更多地了解行为设计模式,尤其是。战略

我会做这样的第一种情况来删除条件,从而删除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.
}
于 2010-12-17T09:32:41.867 回答
2

我不是 C# 程序员,但我会尝试一下。

在第一种情况下,我会说对象首先不应该为空。如果这是不可避免的(通常是可以避免的),那么我将使用提前返回模式:

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

第二种情况显然是不现实的代码,但我至少宁愿说:

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

而不是使用两个单独的 if。

在最后一种情况下,我会将要测试的字符串存储在数组/向量/映射中,并使用该容器方法进行搜索。

最后,虽然使用圈复杂度是“一件好事”(tm)并且我自己使用它,但有些功能自然必须有点复杂 - 验证用户输入就是一个例子。我经常希望我使用的 CC 工具(http://www.campwoodsw.com上的源监视器- 免费且非常好)支持我知道必须复杂且我不希望它的功能的白名单旗帜。

于 2010-12-17T09:38:53.547 回答
1

案例 2中的最后一个 if可以简化:

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

可以替换为

if(isTrue && (e>1))

案例3可以重写为:

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

您甚至可以将字符串数组转换为 aHashSet<String>以获得 O(1) 性能。

于 2010-12-17T09:35:42.223 回答