对于具有大量决策语句(包括if / while / for语句)的方法,Cyclomatic Complexity会很高。那么我们如何改进呢?
我正在处理一个大型项目,在这个项目中,我应该为具有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();
}
答案 0 :(得分:13)
案例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))
{
...
}
答案 1 :(得分:9)
您应该使用重构Replace Conditional with Polymorphism来减少CC。
条件多态代码之间的区别在于多态代码是在运行时做出的决定。这使您可以更灵活地添加\ change \ remove条件而无需修改代码。您可以使用单元测试单独测试行为,从而提高可测试性。此外,由于条件代码较少意味着代码易于阅读且CC较少。
更多关注behavioral design patterns esp。 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.
}
答案 2 :(得分:2)
我不是C#程序员,但我会抓住它。
在第一种情况下,我会说对象首先不应该为空。如果这是不可避免的(通常是可以避免的)那么我会使用早期的返回模式:
if ( objectA == NULL ) {
return;
}
// rest of code here
第二种情况显然不是现实的代码,但我至少应该说:
if ( isTrue && e > 1 ) {
DoStuff();
}
而不是使用两个单独的ifs。
在最后一种情况下,我会将要测试的字符串存储在数组/向量/映射中,并使用容器方法进行搜索。
最后,虽然使用圈复杂度是“好事”(tm)并且我自己使用它,但是有些功能自然要有点复杂 - 验证用户输入就是一个例子。我经常希望我使用的CC工具(http://www.campwoodsw.com上的源监视器 - 免费且非常好)支持我知道必须复杂且我不希望它标记的功能的白名单。
答案 3 :(得分:1)
案例2 中的最后一个可以简化:
if(isTrue)
{
if(e > 1)
{
可以替换为
if(isTrue && (e>1))
案例3 可以改写为:
new string[]{StringConstants.AAA,...}
.Contains(e.PropertyName)
你甚至可以将字符串数组变成HashSet<String>
以获得O(1)性能。