Visual Studio Code指标和switch case的可维护性指数

11
作为一个喜欢遵循最佳实践的人,
如果我在解决方案资源管理器中右键单击项目名称并选择"计算代码度量"(Visual Studio 2010),则可以运行代码指标:
    public static string GetFormFactor(int number)
    {
        string formFactor = string.Empty;
        switch (number)
        {
            case 1:
                formFactor = "Other";
                break;

            case 2:
                formFactor = "SIP";
                break;

            case 3:
                formFactor = "DIP";
                break;

            case 4:
                formFactor = "ZIP";
                break;

            case 5:
                formFactor = "SOJ";
                break;
        }

        return formFactor;
    }

它给我一个可维护性指数61

(当然如果你只有这个的话,这是无关紧要的。但是如果你使用像类这样的实用程序其哲学就像这样做,那么你的实用程序类的可维护性指数将更糟糕..)

这个问题该如何解决?

7个回答

26

首先:61被认为是可维护的代码。对于可维护性指数,100表示非常容易维护的代码,而0表示难以维护。

  • 0-9 = 难以维护
  • 10-19 = 中等维护难度
  • 20-100 = 容易维护

可维护性指数基于三个代码指标:Halstead 大小、圈复杂度和代码行数,并基于以下公式计算:

MAX(0,(171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(Lines of Code))*100 / 171)

事实上,在您的示例中,可维护性指数低的根本原因是圈复杂度。该指标基于代码中各种执行路径的计算。不幸的是,该指标不一定与代码的“可读性”相匹配。

使用圈复杂度评估代码会导致示例中的代码产生非常低的指数值(请记住,较低的值意味着更难以维护),但实际上它们非常易读。这在使用圈复杂度评估代码时很常见。

想象一下,代码有一个用于星期(星期一至星期日)的 switch 块和一个用于月份(1 月至 12 月)的 switch 块。这个代码将会非常易读且易于维护,但是会导致极高的圈复杂度,因此在 Visual Studio 2010 中呈现出非常低的可维护性指数。

这是该指标的一个众所周知的事实,如果基于这些数字进行代码评估,则应予以考虑。应该监控这些数字随时间的变化来理解代码变化的指示器。例如,如果可维护性指数一直为100,然后突然降到10,则应检查导致这种情况发生的变更。


注意:该指数基于 HV、CC 和 LOC 的平均值。有关可维护性指数、系数、阈值及其历史的进一步分析,请参见我的博客文章“在使用可维护性指数前三思”。 - avandeursen

5

由于你选择的解决方案缺乏可扩展性,因此可维护性指数可能会更高。

正确的解决方案(如Mark Simpson所述)是可以扩展以使用新的形式因素而无需重建代码的解决方案-在代码中使用switch / case语句总是意味着面向对象设计已被遗忘,并且应始终被视为糟糕的代码气味。

个人而言,我会实现...

interface IFormFactor
{
    // some methods
}

class SipFormFactor : IFormFactor...

class DipFormFactor : IFormFactor...

Etc.

通过实现接口并在接口上提供所需功能的方法,可以将其视为类似于GoF命令模式。

这样,您的高层方法就可以只是...

MyMethod(IFormFactor factor)
{
    // some logic...

    factor.DoSomething();

    // some more logic...
}

使用这种方式,即使在以后的某个时间点引入了新的形态因素,也不必像硬编码开关语句那样更改此代码。同时,您会发现这种方法也很容易适用于测试驱动开发(TDD)(如果您正确地进行TDD,应该是这样的),因为它很容易模拟。


3

我会这样做,不考虑可维护性指数:

public static string GetFormFactor(int number)
{
    switch (number)
    {
        case 1: return "Other";
        case 2: return "SIP";
        case 3: return "DIP";
        case 4: return "ZIP";
        case 5: return "SOJ";
    }

    return number.ToString();
}

在我看来,易读易改是非常重要的。


这是我做的方式...但我真的想知道是否有另一种解决方案。 - pee2002
我发现下面提到的数组和枚举方法不够易读和易维护。 - David Perlman

3

我知道这个答案很晚了,但是我很感兴趣,因为还没有人提出字典解决方案。我发现,在处理像这样以数据为导向的大型switch语句时,将switch-case折叠成字典通常更易读。

public static readonly IDictionary<int, string> Factors = new Dictionary<int, string> {
   { 1, "Other" },
   { 2, "SIP" },
   { 3, "DIP" },
   { 4, "ZIP" },
   { 5, "SOJ" }
};

public static string GetFormFactor2(int number)
{
   string formFactor = string.Empty;
   if (Factors.ContainsKey(number)) formFactor = Factors[number];
   return formFactor;
}

这将为您提供一个可维护性指数为74——与数组解决方案相比略低,因为它与字典的类耦合,但我认为它更通用,因为它适用于您通常会切换的任何类型数量。与数组解决方案一样,它具有良好的可扩展性,并且可以消除大量重复代码。
一般而言,使用数据驱动方法可以使您的代码更清晰,因为它将重要部分(在本例中是条件和结果)与琐碎部分(在本例中是switch-case)分离开来。

2

有两件事需要注意:

使用枚举将描述和值对应起来

public enum FormFactor
{
    Other = 1,
    SIP = 2,
    etc.
}

使用类或结构来表示每个形态因素。
public class FormFactor 
{
    public int Index { get; private set; }
    public string Description { get; private set; }

    public FormFactor(int index, string description)
    {
        // do validation and assign properties
    }
}

嗯,我有些难以理解你的解决方案。你能否再具体一些?可以给一个更详细的例子吗?谢谢。 - pee2002

0

我不知道这有多重要,但以下得到了76分:

private static string[] _formFactors = new[] { null, "Other","SIP","DIP", "ZIP", "SOJ"};
public static string GetFormFactor2(int number)
{
    if (number < 1 || number > _formFactors.Length)
    {
        throw new ArgumentOutOfRangeException("number");
    }

    return _formFactors[number];
}

是的,这是一个解决方案,但不太优雅:P 我在考虑用结构体的方式来做这个。有什么想法吗? - pee2002

0

对我来说,枚举方法最易于维护,因为它不涉及硬编码字符串,因此没有拼写错误问题和编译时语法检查。唯一的限制是命名规则。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接