降低选择中的圈复杂度

Posted

技术标签:

【中文标题】降低选择中的圈复杂度【英文标题】:Reducing Cyclomatic Complexity in a select 【发布时间】:2021-11-29 12:18:45 【问题描述】:

编码之神早安,

我正在尝试清理一些代码,并且想知道如何降低我创建的方法的循环复杂度。此方法在导入 CSV 文件期间使用。 CSV 文件中的一个字段是许可证类型,它是一个字符串(即“BFI 监督贷方 - 网站 #2 许可证”)我将其转换为将保存到的整数(1、2、3 或 4)数据库以根据许可证类型引用行业类型。

下面是我的方法。希望有一些树荫树 VB.NET 编码器的提示...

Private Function CheckIndustryType(LicName)
    Dim VAR_IndType As Integer

    Select Case Text_LicName
        Case "BFI Level I Check Cashing - Branch License"
            VAR_IndType = 3
        Case "BFI Level II Check Cashing - Branch Certificate"
            VAR_IndType = 3
        Case "BFI Supervised Lender - Branch License"
            VAR_IndType = 1
        Case "BFI Deferred Presentment - Branch License"
            VAR_IndType = 3
        Case "BFI Supervised Lender - Website #1 License"
            VAR_IndType = 1
        Case "BFI Supervised Lender - Website #2 License"
            VAR_IndType = 1
        Case "BFI Supervised Lender - Website #3 License"
            VAR_IndType = 1
        Case "BFI Supervised Lender - Website #4 License"
            VAR_IndType = 1
        Case "BFI Supervised Lender - Website #5 License"
            VAR_IndType = 1
        Case "BFI Supervised Lender - Website #6 License"
            VAR_IndType = 1
        Case "BFI Level II Check Cashing - Company License"
            VAR_IndType = 3
        Case "BFI Level I Check Cashing - Company License"
            VAR_IndType = 3
        Case "fI Branch Mortgage Lender/Servicer"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #1"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #2"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #3"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #4"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #5"
            VAR_IndType = 2
        Case "BFI Branch Mortgage Lender/Servicer - Other Trade Name #6"
            VAR_IndType = 2
        Case "BFI Mortgage Lender / Servicer License"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #1"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #2"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #3"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #4"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #5"
            VAR_IndType = 2
        Case "BFI Mortgage Lender/Servicer License - Other Trade Name #6"
            VAR_IndType = 2
        Case Else
            VAR_IndType = 4
    End Select

    Return VAR_IndType 
End Function

【问题讨论】:

我会避免进行任何更改(除非您可以将它们自动化)。由于输入错误,您可能会引入至少一个错误。您已经将其封装到一个函数中。 (另外:我对计算出的复杂性有点怀疑;它可能是基于这在幕后变成了 if-then-else 树,但我不认为这是对实际心理的准确反映处理函数的负载。) @Craig 在某些情况下,我读到可以将 Select Case 转换为查找表。它发生在带有字符串的 C# 中,但我找不到关于该主题的权威文章。 (并且在调试模式下不进行优化。) 如果您重构此代码,请确保您已经进行了大量的单元测试,这样您就可以完全自信地捕捉到您引入的任何错误。 【参考方案1】:

在这种情况下,我会使用字典。它将独立路径的数量减少到 2,第二个是必要的,因为您当然使用的是默认值。

Module Module1
    Dim industryTypeMap As New Dictionary(Of String, Int32) From "BFI Level I Check Cashing - Branch License", 3,
                                                                           "BFI Level II Check Cashing - Branch Certificate", 3,
                                                                           "BFI Supervised Lender - Branch License", 1

    Sub Main()
        Console.WriteLine(CheckIndustryType("BFI Supervised Lender - Branch License"))
        Console.WriteLine(CheckIndustryType("BFI Level II Check Cashing - Branch Certificate"))
        Console.WriteLine(CheckIndustryType("Other"))
    End Sub

    Private Function CheckIndustryType(LicName As String)
        Dim industryInt As Int32
        If industryTypeMap.TryGetValue(LicName, industryInt) Then
            Return industryInt
        Else
            Return 4
        End If
    End Function
End Module

这个输出:

1
3
4

您还可以在函数中定义字典以将所有代码维护在一起,但如果您重复调用该函数,显然会运行得更慢。

根据下面的评论 - 理想情况下,您会将实际映射放置在无需重新编译代码即可更新的外部项目(例如配置文件或数据库)中。

【讨论】:

最好将数字 非常好的点@AndrewMorton - 如果数据库或配置文件可能发生变化,那么它真的应该用于此目的。有朝一日,任何业务都可能发生变化。【参考方案2】:

我通过使用StartsWithContainsSelect Case True 来缩短案例数。

Private Function CheckIndustryType(LicName As String) As Integer
    Dim VAR_IndType As Integer

    Select Case True
        Case LicName.Contains("Check Cashing")
            VAR_IndType = 3
        Case LicName.StartsWith("BFI Supervised Lender")
            VAR_IndType = 1
        Case LicName.StartsWith("BFI Deferred Presentment")
            VAR_IndType = 3
        Case LicName.StartsWith("fI")
            VAR_IndType = 2
        Case LicName.StartsWith("BFI Branch Mortgage Lender")
            VAR_IndType = 2
        Case Else
            VAR_IndType = 4
    End Select

    Return VAR_IndType
End Function

【讨论】:

Select Case True 并没有真正做任何事情。在这种情况下,使用一系列If 语句似乎更好。 @JoshuaFrank 我不同意它没有做任何事情。它利用隐式转换为If-Then-Else 树,在这种情况下它可能更可取,因为它使逻辑结构更加明显(以弄清楚它在做什么的精神负担为代价)你以前没见过这个成语)。 @JoshuaFrank “实际上什么也没做”是什么意思? @Mary:我的意思是在Select Case True 中,True 始终为 True,因此与编写 If condition else if condition2 else if condition 3 相比,这有点令人困惑。但正如 OP 所说,一开始就不希望有一长串测试,所以稍微不同的编码可能不是最好的结果。【参考方案3】:

一长串长字符串是有风险的,因为任何更改都很可能引入错字,然后字符串不匹配。我可能会做一个Enum

Enum LicenseType
  BFILevelICheckCashingBranchLicense
  BFILevelIICheckCashingBranchCertificate
  BFISupervisedLenderBranchLicense
  ...
End Enum

然后针对该值进行测试。为了降低复杂性,我会创建一个类似的属性:

Class VarIndTypeAttribute 
  Inherits System.Attribute

    Public VarIndType As Integer
    Sub New(varIndType As Integer
        Me.VarIndType = varIndType
    End Sub

End Class

然后枚举变为:

Enum
  <VarIndType(3)>
  BFILevelICheckCashingBranchLicense
  <VarIndType(3)>
  BFILevelIICheckCashingBranchCertificate
  <VarIndType(1)>
  BFISupervisedLenderBranchLicense
  ...
End Enum

然后你制作一个类似的方法

    <Extension>
    Function ToVarIndType(enumValue As [Enum]) As Integer
        Dim att = enumValue.GetAttributeOfType(Of VarIndTypeAttribute)()
        Return If(att IsNot Nothing, att.VarIndType, 0)
    End Function

然后

    Dim valueEnum As LicenseType = ...
    Dim VarIndType = valueEnum.ToVarIndType

而且你根本不需要查找功能!

您可能希望 VarIndType 也成为 Enum,因此您在整个应用程序中没有像 0 和 3 这样的神奇值。

【讨论】:

以上是关于降低选择中的圈复杂度的主要内容,如果未能解决你的问题,请参考以下文章

计算 Javascript 的圈复杂度 [关闭]

高圈复杂度会影响构建时间吗?

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

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

圈复杂度和McCabe

软件测试圈复杂度