使用'goto'语句不好吗?

Ras*_*org 50 c# loops goto break

在做了一些关于如何突破二次循环的研究之后

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary loop
       // Do Something
       break; // Break main loop?
   }
}
Run Code Online (Sandbox Code Playgroud)

大多数人建议调用'goto'函数
看如下例:

while (true) { // Main Loop
   for (int I = 0; I < 15; I++) { // Secondary Loop
       // Do Something
       goto ContinueOn; // Breaks the main loop
   }
}
ContinueOn:
Run Code Online (Sandbox Code Playgroud)

然而; 我经常听说'goto'声明是不好的做法.下图完美地说明了我的观点: 系列发现

所以

  • goto语句真的有多糟糕,为什么?
  • 是否有一种比使用'goto'语句更有效的方法来打破主循环?

Jon*_*eet 49

编辑:

goto语句真的有多糟糕,为什么?

这取决于具体情况.我记不起任何时候我发现它使代码比重构更具可读性.它还取决于你个人对可读性的看法 - 有些人比其他人更不喜欢它,从其他答案可以清楚地看出.(作为一个兴趣点,它被广泛用于生成的代码中--C#5中的所有异步/等待代码都基于有效的许多gotos).

问题在于goto倾向于使用的情况往往是重构帮助事物的情况 - 而goto坚持使用解决方案随着代码变得更加复杂而变得更难以遵循.

是否有一种比使用'goto'语句更有效的方法来打破主循环?

绝对.将您的方法提取到一个单独的函数中:

while (ProcessValues(...))
{
    // Body left deliberately empty
}

...

private bool ProcessValues()
{
   for (int i = 0; i < 15; i++)
   {
       // Do something
       return false;
   }
   return true;
}
Run Code Online (Sandbox Code Playgroud)

我通常更喜欢这样做而不是引入一个额外的局部变量来跟踪"我已经完成" - 当然,这会起作用.

  • @MarceloAssis你会复发什么?他的循环要么返回true/false,要么继续基于此.我觉得它根本不会让人感到困惑. (4认同)
  • 理想情况下,方法应该是可重用的.至少它们应该是逻辑上不同的工作单元.我认为这不符合这些资格.这种解决方案使得代码更难以阅读和理解,仅仅是因为对"goto"语句的非理性恐惧 - 这种恐惧是20世纪70年代的遗留物. (4认同)
  • @MgSam:它绝对是一个独特的工作单元:它执行一个处理值的循环,并返回是否还有更多工作要做.如果我们有更多的背景,我当然可以给它一个更好的名字,但我们真的不知道这里发生了什么.至于可重用 - 我绝对没有*提取私有方法的问题,这些方法只能从另一种方法调用,当它增强了可读性时 - 我相信它在这种情况下会这样做. (3认同)
  • 我并不是说它不好,它确实是一个聪明的解决方案。但我花了很多时间才明白。 (2认同)
  • 我实际上认为 `goto` 是一种更直接的跳出主循环的方式,我没有看到使用它的真正缺点 (2认同)

MgS*_*Sam 36

我将非常不同意这里的所有其他答案.您使用的代码goto没有任何问题.C#有一个声明的原因goto,正是您所描述的这些类型的场景.

goto只是有一个负面的耻辱,因为在20世纪70年代和以前的人会写出可怕的,完全不可维护的代码,其中控制流量遍布整个地方因为goto.C#goto甚至不允许在方法之间转换!然而,仍有这种不合理的耻辱.

在我看来,使用"现代" goto打破内循环绝对没有错.人们提供的"替代品"总是变得更加复杂和难以阅读.

通常认为方法是可重用的.为循环的内部部分创建一个完全独立的方法,只能从该位置调用,并且方法实现可能最终位于源代码中的某个远程位置,这不是一种改进.

要避免这种愚蠢的运动goto基本上等同于政治正确性,但在编程世界中.

  • 该wiki链接中的内容很大程度上不适用于C#.正如我所提到的,你不能在C#中使用`goto`跳转方法.对于像C或C++这样的语言,你可以用`goto`来做这些疯狂的事情,我同意这很危险.在C#中,它**存在**主要是为了打破嵌套循环. (2认同)

Esa*_*ija 34

goto语句真的有多糟糕,为什么?

由于给出的所有正常原因,这真的很糟糕.在不支持它们的语言中模拟标记循环时,它是完美的.

在许多情况下,用函数替换它会分散真正应该作为相同单元读取的逻辑.这使得阅读更加困难.没有人喜欢在行程结束之前跟踪一些没有做任何事情的功能,当你有点忘记你从哪里开始时.

用布尔值和一堆额外的ifs和休息替换它真的很笨重,并且更难以遵循真实的意图,就像任何噪音一样.

java(和javascript)中,这是完全可以接受的(标记为循环):

outer: while( true ) {
    for( int i = 0; i < 15; ++i ) {
        break outer;
    }
}
Run Code Online (Sandbox Code Playgroud)

在C#中,看起来非常接近的等价物不是:

while( true ) {
   for (int I = 0; I < 15; I++) { 
       goto outer;
   }
}
outer:;
Run Code Online (Sandbox Code Playgroud)

因为这个词goto具有心理效应,使人们放弃所有常识,并使他们无论上下文如何链接xkcd.

是否有一种比使用'goto'语句更有效的方法来打破主循环?

在某些情况下没有,这就是其他语言提供标记循环和C#提供的原因goto.请注意,您的示例过于简单,并且使得解决方案看起来不太糟糕,因为它们是根据示例进行定制的.事实上,我也可以这样建议:

   for (int I = 0; I < 15; I++) {
       break;
   }
Run Code Online (Sandbox Code Playgroud)

这个怎么样:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            goto outer;
        }
        val = val / 2;
    }
}
outer:;
Run Code Online (Sandbox Code Playgroud)

这对你来说仍然很好看:

int len = 256;
int val = 65536;

for (int i = 0; i < len; i++)
{
    if (!Inner(i, ref val, len))
    {
        break;
    }
}

private bool Inner(int i, ref int val, int len)
{
    for (int j = 0; j < len; j++)
    {
        if (i + j >= 2 * val)
        {
            return false;
        }

        val = val / 2;
    }

    return true;
}
Run Code Online (Sandbox Code Playgroud)

  • 用于制作有效案例的+1,将`goto`与`return`和`break`进行比较.看看`goto`和不需要的副作用可能会错过使用,与可能的错失使用次数相比,可以使用`return`或`break`,我可以看到为什么`goto`不是最喜欢的.然而,这并不是因为"goto"肯定存在问题,而是因为它会错误地使用它.我认为虽然你的帖子提出了一个非常有效的观点. (5认同)

H_s*_*red 7

我的一个同事(在固件编程方面有 15 年以上的经验)和我一直使用 goto。但是,我们只用它来处理异常!!例如:

if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...

setsockopt_failed:
close(sd);
Run Code Online (Sandbox Code Playgroud)

据我所知,这是在 C 中处理异常的最佳方式。我相信,我也为 C# 工作。此外,我不是唯一一个这么认为的人:C 或 C++ 中好的 goto 示例


Abd*_*him 7

我有时使用"goto",我发现它看起来不错,就像上面的例子;

bool AskRetry(Exception ex)
{
  return MessageBox.Show(you know here...) == DialogResult.Retry;
}

void SomeFuncOrEventInGui()
{
  re:try{ SomeThing(); }
  catch (Exception ex)
  {
    if (AskRetry(ex)) goto re;
    else Mange(ex); // or throw or log.., whatever...
  }
}
Run Code Online (Sandbox Code Playgroud)

我知道你可以递归地做同样的事情,但谁关心它只是工作而我使用.

  • UI 驱动的重试逻辑往往会把结构良好的方法弄得一团糟,goto 可以真正简化流程。也就是说,我们有一个庞大的代码库,使用它们的需求只出现了十几次。我的建议是:如果其他构造不符合要求,请使用它们,但问题是使用它们,它们真的简化了代码吗? (3认同)
  • 同意并投了赞成票。许多重试循环使用 goto 语句更容易阅读。正如其他人所说,使用一堆名为“成功”或类似的局部变量比使用 goto 重试操作要笨重得多。 (2认同)

ble*_*aky 6

要添加到此处的其他答案,除了打破嵌套循环之外,goto 的一种巧妙用法是简单而干净的状态机:

goto EntryState;

EntryState:
//do stuff
if (condition) goto State1;
if (otherCondition) goto State2;
goto EntryState;

State1:
//do stuff
if (condition) goto State2;
goto State1;

State2:
//do stuff
if (condition) goto State1;
goto State2;
Run Code Online (Sandbox Code Playgroud)

这是一种非常干净且可读的状态机方式,而且最重要的是它的性能友好并且免费!虽然您可以在不使用 goto 的情况下执行此操作,但它实际上只会降低代码的可读性。不要回避 goto,只是在使用它之前想一下。


Mar*_*sis 5

我同意大多数关于goto有多糟糕的答案.

我避免goto的消化是这样的:

while (condition) { // Main Loop
   for (int i = 0; i < 15; i++) { // Secondary loop
       // Do Something
       if(someOtherCondition){
              condition = false // stops the main loop
              break; // breaks inner loop
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 但是,在这种情况下,除了更复杂的代码和更高的内存使用量之外,您从中真正避免了什么? (2认同)