为什么我会重构这个代码,因为Cyclomatic Complexity是58

时间:2011-10-18 22:14:00

标签: c# cyclomatic-complexity

我读过CC 10或更低版本的代码是高度可维护的。但是我写的方法有CC 58.感谢VS 2010代码分析工具。我认为,就我的理解而言,我所写的方法非常简单,易读且易于维护。因此,我不希望重构代码。但由于CC高于可接受的水平,我想知道为什么会重构这种方法。我正在学习如何改进我的代码如果我有错误,请纠正我。这是代码。

private string MapBathRooms(string value)
    {
        double retValue = 0;
        if (value == "1" || value == "One")
            retValue = 1;
        if (value == "OneAndHalf" || value == "1.5" || value == "1 1/2")
            retValue = 1.5;
        if (value == "2" || value == "Two")
            retValue = 2;
        if (value == "TwoAndHalf" || value == "2.5" || value == "2 1/2")
            retValue = 2.5;
        if (value == "3" || value == "Three")
            retValue = 3;
        if (value == "ThreeAndHalf" || value == "3.5" || value == "3 1/2")
            retValue = 3.5;
        if (value == "4" || value == "Four")
            retValue = 4;
        if (value == "FourAndHalf" || value == "4.5" || value == "4 1/2")
            retValue = 4.5;
        if (value == "5" || value == "Five" || value == "FourOrMore")
            retValue = 5;
        if (value == "FiveAndHalf" || value == "5.5" || value == "5 1/2")
            retValue = 5.5;
        if (value == "6" || value == "Six")
            retValue = 6;
        if (value == "SixAndHalf" || value == "6.5" || value == "6 1/2")
            retValue = 6.5;
        if (value == "7" || value == "Seven")
            retValue = 7;
        if (value == "SevenAndHalf" || value == "7.5" || value == "7 1/2")
            retValue = 7.5;
        if (value == "8" || value == "8+" || value == "Eight" || value == "SevenOrMore")
            retValue = 8;
        if (value == "EightAndHalf" || value == "8.5" || value == "8 1/2")
            retValue = 8.5;
        if (value == "9" || value == "Nine")
            retValue = 9;
        if (value == "NineAndHalf" || value == "9.5" || value == "9 1/2")
            retValue = 9.5;
        if(value == "10" || value == "Ten")
            retValue = 10;
        if (value == "TenAndHalf" || value == "10.5" || value == "10 1/2"
            || value == "10+" || value == "MoreThanTen" || value == "11")
            retValue = 10.5;

        if (retValue == 0)
            return value;

        return retValue.ToString();
    }

6 个答案:

答案 0 :(得分:15)

为什么不只有Dictionary<string, double>?这将使更多更简单的代码 - 您已将数据与查找代码分开。

private static readonly Dictionary<string, double> BathRoomMap =
    new Dictionary<string, double>
{
    { "1", 1 },
    { "One", 1 },
    { "OneAndHalf", 1.5 },
    { "1.5", 1.5 },
    { "1 1/2", 1.5 }
    // etc
};

private static string MapBathRooms(string value)
{
    double result;
    if (!BathRoomMap.TryGetValue(value, out result))
    {
        return value; // Lookup failed
    }
    return result.ToString();
}

事实上,你可以通过避免ToString调用使其变得更简单 - 只需将其设为Dictionary<string, string>

private static readonly Dictionary<string, string> BathRoomMap =
    new Dictionary<string, string>
{
    // Note: I've removed situations where we'd return the
    // same value anyway... no need to map "1" to "1" etc
    { "One", "1" },
    { "OneAndHalf", "1.5" },
    { "1 1/2", "1.5" }
    // etc
};

private static string MapBathRooms(string value)
{
    string result;
    if (!BathRoomMap.TryGetValue(value, out result))
    {
        return value; // Lookup failed
    }
    return result;
}

正如ChrisF所说,您也可以从文件或其他资源中读取此内容。

这样做的好处:

  • 很多更容易避免错误并扩展,IMO。从输入到输出有一个简单的1:1映射,而不是可能出错的逻辑
  • 它将数据与逻辑分开
  • 如果需要,它允许您从其他地方加载数据。
  • 由于集合初始化程序使用Dictionary<,>.Add,如果您有重复的密钥,则在初始化类型时会出现异常,因此您将立即发现错误。

这样说 - 你会考虑从基于字典的版本重构到“大量真实代码”版本吗?我当然不会。

如果你真的想要在方法中使用它,你总是可以使用switch语句:

private static string MapBathRooms(string value)
{
    switch (value)
    {
        case "One":
            return "1";
        case "OneAndHalf":
        case "1 1/2":
            return "1.5";
        ...
        default:
            return value;
    }
}

我自己仍然使用字典表单...但这确实有一个非常小的优点,即重复检测被提前到编译 -time。

答案 1 :(得分:3)

我同意其他关于使用字典进行映射的海报,但我也想指出像这样的代码通常很难找到错误。例如:

  • 您将“FourOrMore”转换为5,但“MoreThanTen”转换为10.5。这似乎不一致。
  • 您将“11”转换为10.5,这似乎与代码的其余部分不一致。

进行转换的一般算法最初可能难以编写,但从长远来看可以轻松节省时间。

答案 2 :(得分:0)

呀。确实非常可维护。

请改为尝试:

// initialize this somewhere
IDictionary<string, string> mapping;

private string MapBathRooms(string value)
{
  if (mapping.ContainsKey(value))
  {
    return mapping[value];
  }
  return value;
}

将此保留在字典中应该将CC保持为2.可以通过从文件或其他资源中读取字典来初始化字典。

CC(几乎)是方法的潜在执行路径的数量。你对该方法的CC很高,因为你没有使用适合处理这类问题的结构(这里是一本字典)。使用适当的数据结构来解决问题可以使代码保持整洁和可重用。

答案 3 :(得分:0)

回答原因而不是如何:

我在评论Jon Skeet的答案时提到了一个原因,但使用字典和外部资源可以修改应用程序的行为,而无需在每次需求更改时重建它。

另一个是执行速度。您的代码必须检查几十个字符串才能找到结果 - 虽然有一些方法可以在找到匹配项后停止执行,但您仍然需要检查它们。无论输入如何,使用字典都可以为您提供线性访问时间。

答案 4 :(得分:0)

根据DRY原则(不要重复),您可以用if替换所有switch语句。该交换机将使用哈希表实现,因此它也将比所有if语句更快。

您可以删除捕获数字的数字表示的所有情况,因为它由回退处理。

我没有看到将字符串转换为数字,然后再返回字符串的要点。使用文字字符串(因为它们是预先创建的)比在运行中创建字符串更有效。此外,这消除了文化问题,例如,对于某些文化,值9.5将导致字符串"9,5"而不是"9.5"

private string MapBathRooms(string value) {
  switch (value) {
    case "One": value = "1"; break;
    case "OneAndHalf":
    case "1 1/2": value = "1.5"; break;
    case "Two": value = "2"; break;
    case "TwoAndHalf":
    case "2 1/2": value = "2.5"; break;
    case "Three": value = "3"; break;
    case "ThreeAndHalf":
    case "3 1/2": value = "3.5"; break;
    case "Four": value = "4"; break;
    case "FourAndHalf":
    case "4 1/2": value = "4.5"; break;
    case "Five":
    case "FourOrMore": value = "5"; break;
    case "FiveAndHalf":
    case "5 1/2": value = "5.5"; break;
    case "Six": value = "6"; break;
    case "SixAndHalf":
    case "6 1/2": value = "6.5"; break;
    case "Seven": value = "7"; break;
    case "SevenAndHalf":
    case "7 1/2": value = "7.5"; break;
    case "8+":
    case "Eight":
    case "SevenOrMore": value = "8"; break;
    case "EightAndHalf":
    case "8 1/2": value = "8.5"; break;
    case "Nine": value = "9"; break;
    case "NineAndHalf":
    case "9 1/2": value = "9.5"; break;
    case "Ten": value = "10"; break;
    case "TenAndHalf":
    case "10 1/2":
    case "10+":
    case "MoreThanTen":
    case "11": value = "10.5"; break;
  }
  return value;
}

请注意,我将输入"11"的结果保留为"10.5"的返回值。我不确定这是不是一个错误,但这就是原始代码的作用。

答案 5 :(得分:0)

对于您的一般问题,对于其他响应者对此特定功能无法重构的其他情况,有一种CC的变体将案例陈述统计为单个分支,理由是它几乎与易于理解的线性代码行(虽然不适用于测试覆盖)。测量一种变体的许多工具将提供另一种。我建议使用case = 1变体,或者使用你正在使用的变体。