在C#控制台应用程序中使用GOTO。改变主意,这是个坏主意

时间:2019-05-03 15:02:13

标签: c# console

所以我正在与我的同事交谈,他们说GOTO声明无论如何都是一个坏主意!

我只是想弄清楚社区的想法。

这是我的例子:
我创建了一个具有两个功能的小型控制台应用程序

  

您想做什么?

     
      
  1. 解析文件
  2.   
  3. 退出
  4.   

我的代码类似于下面的伪代码:

Start:
Console.Write("What would you like to do?");
Console.Write("1. Parse a file");
Console.Write("2. Exit");

var key = Console.ReadKey();

if (key == "1")
 GOTO ParseFile;
If (key == "2")
 return null;

ParseFile:
Console.Write("File location");
var fileLocation = Console.ReadLine();
var parsed = parser.Parse(fileLocation);
...do work on parsed..
GOTO Start:

我的想法是让用户(我)不必每次解析文件时都启动应用程序,但是如果我要解析的文件不止一个,我可以重新开始。

>

6 个答案:

答案 0 :(得分:3)

GOTO增加了不必要的复杂性,如果代码更复杂,则可能难以跟踪和修复。 GOTO所做的大多数事情都可以通过带有break语句的现有控制结构来完成。

您可以从代码中提取方法,使其更加模块化且易于修改,而不是将它们全部包含在带有GOTO语句的列表中。

答案 1 :(得分:3)

问题不在这里,在这种情况下,使用if,switch或goto就足够了,但是想象一个没有1或2个条件但有10个可以说的代码,您必须找到该goto标签的位置。这只是goto会引起问题的冰山一角。

当您使用goto时,您将摆脱堆栈跟踪,因此当它执行时,它将转到该点并从该点开始继续执行...因此可能导致灾难

答案 2 :(得分:2)

GOTO破坏了封装并且是延迟编程的标志。用GOTO做坏事比使用适当的编码技术要容易得多。

如果有人写了除2以外的任何内容,您的示例将解析文件,这可能无法通过查看代码看出来,因为它会在第二条if语句之后通过。

如果您改为对ParseFile部分进行方法化,则可以清楚地知道何时调用它,而不是无意中发现它。

控制流应该是故意的,因为错误已经很普遍了,由于懒惰而没有添加更多错误。

答案 3 :(得分:1)

根本没有理由在控制台应用程序中使用goto。 第一个GOTO ParseFile只是一个切换情况下的中断(一个切换情况将更具可读性,因为您可以获得多个中断条件,并且知道它们都在同一个if子句中),GOTO Start只是在应用程序的开头(再次,它会更具可读性,因为您将能够在一秒钟内告诉自己哪些代码块将重复自身,您也将知道为什么应该重复它(使用goto,您不会能够说出为什么要重复执行该代码块,直到您阅读完每行代码,并且知道何时无法到达该goto为止。)

不使用goto的主要原因是“条件跳转”。如果您不知道为什么要跳转代码,一段时间后将很难阅读代码。

此外,您的代码将更加容易。

// FormPartTwo.js

render () {
  const isSubmitDisabled =
    !this.props.Password ||
    !this.props.ConfirmPassword ||
    this.props.Password !== this.props.ConfirmPassword;

  return (
    ...
    <input
      type="submit"
      disabled={isSubmitDisabled}
      onClick={e => this.props.handleClick(e)}
    />
    ...
  )
}

比您的奇怪代码容易得多。 您只要求用户开始(如果不返回就返回),然后继续解析用户是否要继续(不返回时)。

答案 4 :(得分:1)

正如其他人所说,您应该使用switchif,但是如果您认为将来必须扩展应用程序的功能,则应考虑策略模式。它使我们能够通过更改代码来扩展应用程序的功能,并且是遵守开闭原理(SOLID中为O)的完美示例。

每当您看到一个开关或if语句考虑添加另一种情况的难易程度。在问题示例中,是否必须向应用程序策略中添加其他命令将像这样工作。

首先,我们定义一个可以处理命令的接口:

interface ICommandHandler
{
    bool SupportsCommand(string command);
    void ExecuteCommand(string command);
    IEnumerable<string> GetSupportedCommands();
}

接口有几种方法。如果接口的具体实现支持命令,则SupportsCommand返回true。 ExecuteCommand执行命令(如果Task对于您的用例来说不够好,它可以返回void。) GetSupportedCommands仅用于打印出应用程序支持哪些命令。

接下来,我们通过几个处理程序来实现它:

class ParseFileCommandHandler : ICommandHandler
{
    public void ExecuteCommand(string command)
    {
        // ...
    }

    public IEnumerable<string> GetSupportedCommands()
    {
        yield return "parse";
    }

    public bool SupportsCommand(string command)
    {
        return command == "parse";
    }
}

class PrintFileCommandHandler : ICommandHandler
{
    public void ExecuteCommand(string command)
    {
        // ...
    }

    public IEnumerable<string> GetSupportedCommands()
    {
        yield return "print";
    }

    public bool SupportsCommand(string command)
    {
        return command == "print";
    }
}

下一步是定义可用作调用特定ICommandHandler实现的代理的类:

class CommandStrategies
{
    private List<ICommandHandler> _commandHandlers;

    public CommandStrategies()
    {
        _commandHandlers = typeof(CommandStrategies)
            .Assembly
            .GetTypes()
            .Where(x => x.IsClass && !x.IsAbstract && typeof(ICommandHandler).IsAssignableFrom(x))
            .Select(Activator.CreateInstance)
            .Cast<ICommandHandler>()
            .ToList();
    }

    public bool SupportsCommand(string command)
    {
        return _commandHandlers.Any(x => x.SupportsCommand(command));
    }

    public void ExecuteCommand(string command)
    {
        var handler = _commandHandlers.FirstOrDefault(x => x.SupportsCommand(command));
        if (handler != null)
        {
            handler.ExecuteCommand(command);
        }
    }

    public IEnumerable<string> GetSupportedCommands()
    {
        return _commandHandlers
            .SelectMany(x => x.GetSupportedCommands())
            .Distinct();
    }
}

在构造函数中,我们使用反射找到并实例化接口的所有实现。仅当所有实现中都没有带有参数的构造函数且所有实现都在同一程序集中时才有效(如果不是这样,则可以将处理程序的实例传递给CommandStrategies的构造函数。仅仅是对ICommandHandler的实现的代理调用。

只剩下实现基础结构(Console)代码了,我们可以在应用程序的入口类中做到这一点。

class Program
{
    static CommandStrategies CommandHandlers = new CommandStrategies();

    static void Main()
    {
        PrintSupportedCommands();

        while (true)
        {
            Console.WriteLine("Enter command:");
            string command = Console.ReadLine();
            if (command == "exit") return;

            bool isCommandSupported = CommandHandlers.SupportsCommand(command);
            if (!isCommandSupported)
            {
                Console.WriteLine("Command is not supported.");
                PrintSupportedCommands();
            }
            else
            {
                CommandHandlers.ExecuteCommand(command);
            }
        }
    }

    static void PrintSupportedCommands()
    {
        Console.WriteLine("Supported commands are:");
        foreach (var cmd in CommandHandlers.GetSupportedCommands())
        {
            Console.WriteLine(cmd);
        }

        Console.WriteLine("exit");
    }
}

否,如果您需要支持其他命令处理程序,则只需添加另一个包含ICommandHandler的类,其他所有内容便会像以前一样工作,而无需为case添加另一个switch或另一个if else

此解决方案可能是一个过高的选择,但对于应用程序的关键部分而言,这通常是值得的。

答案 5 :(得分:0)

你们改变了主意!
感谢您的周到反馈。这比只读或听“不要做!”要好得多。