【问题标题】:How to reduce cyclomatic complexity for these if else statements如何降低这些 if else 语句的圈复杂度
【发布时间】:2017-04-14 11:20:40
【问题描述】:

我正在尝试验证命令行参数并在出现错误时打印错误消息。

我的问题是,如果增加命令行参数的数量(目前我只有3个),那么我的代码就会变成意大利面条代码。如何降低给定代码的圈复杂度?

var isCmdLineWrong = false;
var Arg1 = "Undefined";
var Arg2 = "Undefined";
var Arg3 = "Undefined";

var commandArguments = Environment.GetCommandLineArgs();
if (commandArguments.Contains("-r") && arguments[commandArguments.IndexOf("-r") + 1].StartsWith("-") == false)
    Arg1 = commandArguments[commandArguments.IndexOf("-r") + 1];
else
{
    isCmdLineWrong = true;
}
if (commandArguments.Contains("-n") && commandArguments[commandArguments.IndexOf("-n") + 1].StartsWith("-") == false)
    Arg2 = commandArguments[commandArguments.IndexOf("-n") + 1];
else
{
    isCmdLineWrong = true;
}
if (commandArguments.Contains("-p") && commandArguments[commandArguments.IndexOf("-p") + 1].StartsWith("-") == false)
    Arg3 = commandArguments[commandArguments.IndexOf("-p") + 1];
else
{
    isCmdLineWrong = true;
}
if (isCmdLineWrong) Console.WriteLine("Parameters structure is inconsistent");

【问题讨论】:

    标签: c# algorithm command-line-arguments cyclomatic-complexity


    【解决方案1】:

    在您的代码中要观察的最重要的事情可能是 您多次执行完全相同的操作,尽管输入不同 "-r"Arg1"-n" 和 @987654325 @、"-p"Arg3。也就是说,您有以下代码片段出现了 3 次(减去我的重新格式化):

    if (commandArguments.Contains(…) &&
        arguments[commandArguments.IndexOf(…) + 1].StartsWith("-") == false)
    {
        … = commandArguments[commandArguments.IndexOf(…) + 1];
    }
    else
    {
        isCmdLineWrong = true;
    }
    

    Don't Repeat Yourself (DRY) principle 试图警告我们不要编写复制粘贴式的重复代码,而您的原始代码显然违反了它。

    我建议你提取公共代码并放在一个单独的方法中。例如:

    static bool TryGetArg(string commandArguments, string name, out string value)
    {
        // Debug.Assert(name.StartsWith("-"));
        if (commandArguments.Contains("-") &&
            arguments[commandArguments.IndexOf(name) + 1].StartsWith("-") == false)
        {
            value = commandArguments[commandArguments.IndexOf(name) + 1];
            return true;
        }
        else
        {
            value = null;
            return false;
        }
    }
    

    现在您将重复的 if else 替换为以下内容:

    string commandArguments = Environment.GetCommandLineArgs();
    
    string arg1 = null;
    string arg2 = null;
    string arg3 = null;
    bool isCmdLineOk = TryGetArg(commandArguments, "-r", out arg1) &&
                       TryGetArg(commandArguments, "-n", out arg2) &&
                       TryGetArg(commandArguments, "-p", out arg3);
    if (isCmdLineOk)
    {
        // do something with `arg1`, `arg2`, `arg3`.
    }
    else
    {
        // not all of `arg1`, `arg2`, `arg3` could be set to a value.
        Console.WriteLine("Parameters structure is inconsistent");
    }
    

    【讨论】:

      【解决方案2】:

      这个问题是一个如何重用代码的简单例子。

      • 查找似乎已被复制/粘贴的代码,
      • 把它放在一个函数中,
      • 副本之间的任何差异,将它们作为参数传递,
      • 用函数调用替换副本。

      结果是

          // Returns this option's value from args, or null on error
          public string OptionValue(string[] args, string option)
          {
              try
              {
                  if (args.Contains(option))
                  {
                      string value = args[args.IndexOf(option) + 1];  // reuse expressions as well
      
                      if (!value.StartsWith("-"))
                          return value;
                  }
      
                  return null;    // null meaning "undefined"
              }
              catch
              {
                  return null;  
              }
           }
      
           // And now your code
           string[] args = Environment.GetCommandLineArgs();
      
           string Arg1 = OptionValue(args, "-r"); 
           string Arg2 = OptionValue(args, "-n"); 
           string Arg3 = OptionValue(args, "-p"); 
      
           bool isCmdLineWrong = (Arg1 == null ||
                                  Arg2 == null ||
                                  Arg3 == null);
      

      当然,如果您一开始没有复制/粘贴代码,所有这些重写都是可以避免的。

      【讨论】:

        【解决方案3】:

        我建议提取CommandLineclass

        public static class CommandLine {
          private static String FindValue(string value) {
            var commandArguments = Environment.GetCommandLineArgs();
        
            int index = commandArguments.IndexOf(value);
        
            if (index < 0)
              return null; 
            else if (index >= commandArguments.Length - 1)
              return null; // cmd like "myRoutine.exe -p" 
            else 
              return commandArguments[index + 1];  
          } 
        
          static CommandLine() {
            Arg1 = FindValue("-r");
            Arg2 = FindValue("-n");
            Arg3 = FindValue("-p");
          } 
        
          public static String Arg1 { get; private set; }
        
          public static String Arg2 { get; private set; }
        
          public static String Arg3 { get; private set; }
        
          public static bool IsValid {
            get {
              return Arg1 != null && Arg2 != null && Arg3 != null;
            }
          }
        }
        

        写完这个类你就可以放了

        if (!CommandLine.IsValid) {
          Console.WriteLine("Parameters structure is inconsistent");
        
          return;
        } 
        
        if (CommandLine.Arg1 == "quit") {
          ...
        }  
        

        【讨论】:

        • 正是我的想法,但你为什么把它全部设为 static
        • @Maarten:我们只有 一个 应用程序命令行,所以我们不想要CommandLine 类的很多实例(如Application、@987654327 @ 和类似的类)
        • @DmitryBychenko:没错,但它也让你的类更难测试或替换。我可以建议这些简单的更改:1. 使类及其成员非静态,2. 使用将使用的commandLine 字符串参数扩充 ctor Environment.GetCommandLineArgs(),最后 3. 添加一个静态 Instance 属性或字段,该属性或字段返回 / 被初始化为与实际命令行相关联的实例:public static CommandLine Instance = new CommandLine(Environment.GetCommandLineArgs())
        • @stakx:你说的很对,对于通用/通用解决方案(例如,为其他一些进程解析和构建命令行)。在这个问题三个参数和一个原始IsValid属性)中,一个简单的static class将是,恕我直言,当您提出的详细实现是在这种情况下出现过冲(在一般情况中是一个更好的解决方案)。
        • @stakx:如果我必须测试(实现单元测试)这样的static class,我的第一个想法是在Environment.GetCommandLineArgs() msdn.microsoft.com/en-us/library/hh549175(v=vs.110).aspx 上放置一个shim(我可以使用 VS 终极版)。手动调用静态构造函数System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(CommandLine).TypeHandle);。我明白你的理由:测试你提出的解决方案要容易得多(只需执行构造函数)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-07
        • 2020-06-13
        相关资源
        最近更新 更多