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



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

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

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

您想做什么?

  1. 解析文件
  2. 退出

我的代码在下面看起来像该伪代码:

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:

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

goto添加了不必要的复杂性,如果代码更复杂,则很难跟踪和修复。GoTOS所做的大多数事情都可以通过已经存在的控制结构和中断语句来完成。

您可以从代码中提取方法,使其更易于修改,而不是用goto语句将其全部放在一个清单中。

问题不在这里,在这种情况下,使用IF,Switch或goto就足够了,但是想象一个没有1或2条条件的代码,而是10,您必须找到该goto标签的位置。这只是一个冰山的尖端。

当您使用goto时,您将摆脱堆栈跟踪,因此,当执行它时,它将进入该点并从那时开始执行...以便可能导致灾难

goto breaks封装,是懒惰编程的标志。与使用适当的编码技术相比,使用goto做坏事是无限容易的。

您的示例将解析文件,如果某人在2以外写了任何内容,而这可能是不明显的,因为它在第二个if语句之后掉落了。

如果您对羊角file部分进行了方法,那么您将在被调用时明确知道,而不是不小心掉落到它。

控制流应该是故意的,因为错误已经太普遍了,而不会因为懒惰而增加更多。

根本没有理由在您的控制台应用程序中使用goto。第一个goto parsefile只是开关案例中的一个断裂(开关案例可以更易读,因为您可以获得多重断裂状态,并且知道它们在同一条款中都在同一条件中,而goto启动只是一个简单的启动启动应用程序(再次一段时间会更可读性,因为您将能够在一秒钟内分辨出代码的哪个块将重复出现,您也可以知道为什么应该重复它(使用Goto,您不会能够告诉为什么要重复该代码块,直到您阅读它的每一行并知道何时无法达到该代码((。

(。

不使用goto的主要原因是"有条件跳跃"。如果您不知道为什么要跳入代码,您的代码很难在一段时间后阅读。

另外,您的代码更容易。

print instruction
if(key != 1)
  return null;
while(key == 1){
  (do not print instruction first time)
  parse...
}
return null;

比奇怪的代码容易得多。您只要求用户启动(并返回(如果没有(,则如果用户想要继续(不返回(,您将继续解析。

正如其他人所说的那样,您应该使用switchif,但是,如果您认为将来必须扩展应用程序的功能,则应考虑策略模式。它使我们能够通过更改代码扩展应用程序的功能,这是尊重开放闭合原理的一个完美示例(o solid(。

每当您看到开关或语言是否考虑添加其他情况有多么容易或难度。在您的问题中的示例中,如果您必须在应用程序策略中添加另一个命令,则可以这样。

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

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

接口有几种方法。SupportsCommand如果由接口的具体实现支持命令,则返回true。ExecuteCommand执行命令(如果void不够好,可以返回Task(。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");
    }
}

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

此解决方案可能是一个过度杀伤,但对于应用程序的关键部分通常值得。

你们已经改变了我的想法!
感谢您的深思熟虑的反馈。

,这比阅读或听到"不做!"要好得多。

相关内容

最新更新