圈复杂度数为 31,这是从哪里来的?

Posted

技术标签:

【中文标题】圈复杂度数为 31,这是从哪里来的?【英文标题】:Cyclomatic complexity count of 31, where has this come from? 【发布时间】:2012-12-06 10:08:46 【问题描述】:

我正在开发一个从 Excel 文件中提取数据的应用程序(我无权访问实际数据库),并且我编写了一个方法,该方法具有从 Excel 电子表格中提取数据的唯一功能,如下所示。

private IEnumerable<SMEntity> ExtractSMData(List<MSExcel.Range> SMData)

    List<SMEntity> SMEntities = new List<SMEntity>();

    foreach (MSExcel.Range Row in SMData)
    
        SMEntity entity = new SMEntity();
        entity.IncidentNumber = Row.get_Range("K1").get_Value();
        entity.SRNumber = Row.get_Range("L1").get_Value();
        entity.SRCategory = Row.get_Range("M1").get_Value();
        entity.SiebelClientCall = EntityConversions.DateTimeConversion(Row.get_Range("N1").get_Value());
        entity.SiebelOpenedDate = EntityConversions.DateTimeConversion(Row.get_Range("O1").get_Value());
        entity.IncidentOpenDate = EntityConversions.DateTimeConversion(Row.get_Range("P1").get_Value());
        entity.PickedUpBeforeClient = Row.get_Range("Q1").get_Value().ToString().ToLowerCase() == "no" ? false : true;
        entity.OutageStartTime = EntityConversions.DateTimeConversion(Row.get_Range("R1").get_Value());
        entity.DetectionPoint = EntityConversions.DateTimeConversion(Row.get_Range("S1").get_Value());
        entity.SecondsToDetection = EntityConversions.ConvertDetectionTimeToInt(Row.get_Range("T1").get_Value());
        entity.OutageEndTime = EntityConversions.DateTimeConversion(Row.get_Range("U1").get_Value());
        entity.MTTR = EntityConversions.ConvertMTTRStringToInt(Row.get_Range("V1").get_Value());
        entity.RepairedOnTime = Row.get_Range("W1").get_Value().ToString().ToLowerCase() == "no" ? false : true;
        SMEntities.Add(entity);
    

    return SMEntities;

我已经运行了代码分析(我正在使用 Visual Studio 2012 并在 .NET 4.5 中进行开发)并且我有一个 CA1502: Avoid excessive complexity(复制如下)。作为一名初级开发人员(我今年 17 岁),我尝试使用 MSDN 了解更多有关此内容的信息,但是,我对为什么我的圈复杂度为 33 感到有些困惑。

CA1502

避免过于复杂

'Extraction.ExtractSMData(List&lt;Range&gt;)' 有一个圈 复杂度为 33. 重写或重构方法以降低复杂度 到 25 岁。

Core.Extraction.cs:104

我可以通过我的快速假设(condition ? if_true : if_false,这些叫什么?)看到它可能很糟糕,但我仍然只能将其视为 5。

更新

圈复杂度现在为 33...

如果我注释掉 entity.IncidentNumber = Row.get_Range("K1").get_Value();,复杂度变为 32。我认为 get_Range()get_Value() 各为一个,但还好...

如果我注释掉 entity.RepairedOnTime = Row.get_Range("W1").get_Value().ToString().ToLower() == "no" ? false : true;,复杂度将变为 28...

get_Range(), get_Value(), quick-if 是 3,ToString()ToLower() 算吗?

【问题讨论】:

您的“quick-ifs”被称为conditional operator。不过,我喜欢你使用“快速如果”这个短语。它很整洁。 ;) @alexh 它不被称为 the 三元运算符 - 它是 条件运算符 它是 a 三元运算符 bool x = Row.get_Range("W1").get_Value().ToString().ToLowerCase() == "no" ? false : true; 可以替换为bool x = Row.get_Range("W1").get_Value().ToString().ToLowerCase() != "no",这不是条件语句,因此您可以稍微降低复杂性。 @Andras Zoltan,感谢您的精确:我们在我国使用“三元”术语来定义此运算符。这是一个错误 此外,谷歌搜索的内容是“?:运算符”,因为冒号是运算符的一部分。 =) 【参考方案1】:

我计算了方法本身的复杂度,foreach 和两个条件运算符总共 4 个。如果对 get_Range 的 13 次调用中的每一个都值得 +1 复杂度,并且对 get_Value 的 13 次调用中的每一个都值得 +1 复杂度,那么总复杂度将加起来为 30(仍然是 1 短,但接近)。我不确定为什么这两个函数会增加复杂性,但这似乎是合理的。

尝试删除调用 get_Rangeget_Value 的行之一,看看圈复杂度是否降至 29。

【讨论】:

FxCop 作用于生成的 IL 代码。我假设编译器会为每个调用插入一些 COM 互操作代码(例如检查空值)。有 28 个 COM 调用(13xget_Range,13xget_value,GetEnumerator,GetNext),2x ?: + foreach = 31 检查更新家伙。我也这么认为,阿德里安,但现在已经 33 岁了!【参考方案2】:

您的返回类型是 IEnumerable,所以不要使用列表。这使得 IENumerable 毫无用处。 否则你不使用惰性评估 见:http://blogs.msdn.com/b/pedram/archive/2007/06/02/lazy-evaluation-in-c.aspx

那么最好使用yield return:

private IEnumerable<SMEntity> ExtractSMData(List<MSExcel.Range> SMData)

    foreach (MSExcel.Range Row in SMData)
    
        SMEntity entity = new SMEntity();

        entity.IncidentNumber = Row.get_Range("K1").get_Value();
        entity.SRNumber = Row.get_Range("L1").get_Value();
        entity.SRCategory = Row.get_Range("M1").get_Value();
        entity.PickedUpBeforeClient = !Row.get_Range("Q1").get_Value().ToString().ToLowerCase() == "no"
        entity.RepairedOnTime = !Row.get_Range("W1").get_Value().ToString().ToLowerCase() == "no"

        entity.SiebelClientCall = EntityConversions.DateTimeConversion(Row.get_Range("N1").get_Value());
        entity.SiebelOpenedDate = EntityConversions.DateTimeConversion(Row.get_Range("O1").get_Value());
        entity.IncidentOpenDate = EntityConversions.DateTimeConversion(Row.get_Range("P1").get_Value());
        entity.OutageStartTime = EntityConversions.DateTimeConversion(Row.get_Range("R1").get_Value());
        entity.DetectionPoint = EntityConversions.DateTimeConversion(Row.get_Range("S1").get_Value());
        entity.OutageEndTime = EntityConversions.DateTimeConversion(Row.get_Range("U1").get_Value());


        entity.MTTR = EntityConversions.ConvertMTTRStringToInt(Row.get_Range("V1").get_Value());
        entity.SecondsToDetection = EntityConversions.ConvertDetectionTimeToInt(Row.get_Range("T1").get_Value());


        yield return entity;
    


你也可以这样写:

private IEnumerable<SMEntity> ExtractSMData(List<MSExcel.Range> SMData)

    foreach (MSExcel.Range Row in SMData)
    
        yield return new SMEntity 
        

            IncidentNumber = Row.get_Range("K1").get_Value(),
            SRNumber = Row.get_Range("L1").get_Value(),
            SRCategory = Row.get_Range("M1").get_Value(),
            PickedUpBeforeClient = !Row.get_Range("Q1").get_Value().ToString().ToLowerCase() == "no"
            RepairedOnTime = !Row.get_Range("W1").get_Value().ToString().ToLowerCase() == "no"

            SiebelClientCall = EntityConversions.DateTimeConversion(Row.get_Range("N1").get_Value()),
            SiebelOpenedDate = EntityConversions.DateTimeConversion(Row.get_Range("O1").get_Value()),
            IncidentOpenDate = EntityConversions.DateTimeConversion(Row.get_Range("P1").get_Value()),
            OutageStartTime = EntityConversions.DateTimeConversion(Row.get_Range("R1").get_Value()),
            DetectionPoint = EntityConversions.DateTimeConversion(Row.get_Range("S1").get_Value()),
            OutageEndTime = EntityConversions.DateTimeConversion(Row.get_Range("U1").get_Value()),


            MTTR = EntityConversions.ConvertMTTRStringToInt(Row.get_Range("V1").get_Value()),
            SecondsToDetection = EntityConversions.ConvertDetectionTimeToInt(Row.get_Range("T1").get_Value())
        ;
    

【讨论】:

惰性求值非常适合简单的内存中的东西。在处理外部依赖项或其他复杂的事情时,您最好渴望能够处理错误。 IEnumerable 的使用者不应该根据方法的实现细节来处理 COMException、SqlExcepton、IOException 等? “源”-数据已经在内存中,因为函数需要一个列表作为参数。我认为这归结为“个人偏好”。如果他想一次失败所有行,或者在可能的错误发生之前检索一些。

以上是关于圈复杂度数为 31,这是从哪里来的?的主要内容,如果未能解决你的问题,请参考以下文章

圈复杂度和McCabe

前端代码质量-圈复杂度原理和实践

dotnet 代码优化 聊聊逻辑圈复杂度

寻求有关结构化代码以降低圈复杂度的说明

.NET 代码优化 聊聊逻辑圈复杂度

降低选择中的圈复杂度