【问题标题】:multiple return statements or a "goto end;"多个返回语句或“goto end”;
【发布时间】:2011-05-20 15:48:05
【问题描述】:

什么是更好的样式/可读性?

我有一个循环读取输入并根据输入做不同的事情,当发生错误时,我需要一个简单的return;。示例:

while( get_input() )
{
    if( input == "somethingcool" )
    {
        if( !process_somethingcool() )
            return; // <-- a couple of these
    }
    //...
    else // bad input, error handling is fancier than this, but irrelevant to the question
        return;
}
return;

那么我是否应该用goto end; 替换单个return;s 并在上面示例中的最后一个返回正上方放置一个标签end:?我不需要“使用 RAII”,因为在 if 块中没有分配任何内容。除了样式/可读性/性能之外,这两种方式在所有意义上都是相同的?

我认为性能是相同的,但只是为了确定:是吗?

【问题讨论】:

  • 这种情况下是否有理由不使用异常?
  • 让毫无根据的goto-bashing 开始吧。 2011 个 cookie 用于逻辑论证。
  • @rubenvb 我觉得它与您的问题相关,因为我认为这是处理您要处理的问题的标准 C++ 方式,如果我正确理解了您想要的.实际上,C++ 异常处理正在做你正在用“goto”做的事情,只是有更多的形式和额外的功能,你可能会发现它很有用。其他 C++ 程序员也可能更熟悉它,根据我的经验,他们中的大多数人使用异常的次数要比使用 goto 的次数多。
  • @James:我不是说goto 是“好”。我不是说它就在这里(事实上,我的回答正好相反)。我知道通常有充分的理由不使用它。事实上,证明程序的正确性在使用无条件跳转时会出现一些问题。然而,我反对的是毫无根据地抨击一个功能,在这个问题的背景下(实际上在其他一些用例中),这个功能非常好。
  • @Dennis:如果没有客观的论据可以提出,这只是主观的。我认为答案和 cmets 总体上表明有很多客观的论据要提出,因此问题本身具有客观的价值。

标签: c++ coding-style return goto


【解决方案1】:

对于 C,goto 是合理的(它在 Linux 内核中广泛使用),因为您可以通过单点返回来增强可读性。

对于 C++,因为您有可能引发异常,所以您隐含了多个返回点,因此您应该始终使用具有多个返回的 RAII 模型。

【讨论】:

  • 这就是为什么成千上万的 C++“程序员”可悲地滥用异常。 :(
  • 鉴于 Linux 的稳健性,我不会将它们在其内核代码中的使用作为参考。
  • @James:呵呵,确实。玩内核代码很有趣。当然没有理由认为它是编程技术的权威。
  • @James 你能扩展一下吗?你是说Linux不健壮吗?对我来说似乎差不多。
  • @Neil:IIRC、truncate()ftrunate() 都是很好的例子。
【解决方案2】:

你想发动一场宗教战争吗?

说真的,有时候 goto 是最好的选择。二十年来,我见过大约 3 或 4 次。多次返回不一定是邪恶的,但如果你必须重复大量的清理代码,那么它们就会变得非常混乱。

很多时候,您可以重构代码以使这样的选择变得不必要。没有看到你的代码很难提出具体的建议,但可能是这样的:

void f()
{
   bool bDone=false;
   while (!bDone && get_input())
   {
      if (input == "cool")
      {
         process_cool();
         bDone = true;
      }
      else if (input == "unfinished")
      {
         process_something();
      }
      else
      {
          // error
          bDone = true;
      }
   }
}

重构的一大帮助是确保循环内没有几十行。如果您有大量工作要做,请将其分解为函数并从 while 循环中调用少量函数。

请记住,单个函数应该只做一件事。

另一种强烈推荐的方法是使用异常来处理错误情况,但如果您刚刚完成处理,使用异常是一种不好的方式,因此这可能无法完全解决您的问题。

如果您仍然感到困惑,请考虑发布一段更真实的代码,我们或许可以建议如何最好地处理这些问题。

祝你好运!

【讨论】:

    【解决方案3】:

    就我个人而言,我喜欢让任何块的内容尽可能小。一行,调用另一个函数是理想的(在 Bob Martin 的 Clean Code 之后)。

    我会选择你没有提出的选项:

    while(get_input()) {
        if(!process_input(input)) {
             break;
        }
    }
    

    其中 process_input 将选择适当的 process_... 函数,返回返回的任何内容,如果输入错误,则返回 false。

    【讨论】:

    • “就个人而言,(在 Bob Martin 的 Clean Code 之后)我喜欢让任何块的内容尽可能小”——这对于可怕的 Martin 来说几乎不是原创的。
    • 你是对的。甚至在阅读 CC 之前,我就喜欢让我的块尽可能小,这应该放在下一句之后(在编辑中移动)。直到我读了 CC,我才得到了恰当的极简主义“一条线”的东西。 (这可能也不是他的原创,但这就是我从那里得到的)。
    【解决方案4】:

    当你想回来的时候就回来。 C++ 语言可以处理这个问题,它是编写代码最直观的方式。

    在某些语言中,清理工作必须在使用站点完成,因此将“清理”阶段集中在您“转到”的单个块中可能是个好主意。在 C 语言中,这种习语很常见。

    在 C++ 中,当资源超出范围时,资源会在其析构函数中自行清理。所以在使用现场,什么都不需要,最简单、最干净、最不容易出错的解决方案就是多条return语句。

    【讨论】:

      【解决方案5】:

      多个返回语句实际上是一种很好的风格,并且几乎总是比尝试使用单个返回点生成更简洁的代码。 gotos 在 C++ 中非常没用,因为(除了它们的其他问题)它们不能跳过初始化,这可能会迫使你在远离它们的使用点的地方进行初始化,这也是一种不好的风格。

      【讨论】:

      • 学点英语。我说的是“完全没用”,而不是“完全没用”,我证明了我所说的。
      • @Neil:我说你没有;你的主张毫无意义。甚至“非常没用”也没有遵循。请不要私信。
      • @Tomalak:Neil 指出 C++ 中的 goto 有几个严重的缺点,实际上可能会在您的代码中产生许多细微的错误,从而在使用时产生强烈的代码气味。在这方面它与 C 完全不同,因为 C++ 有很多规则 WRT 初始化和销毁​​ goto 明确违反。
      • @greyfade 实际上,我并没有指出这一点。 C++ 编译器需要将跳过初始化诊断为错误。我是说使用 goto 几乎肯定会导致您以不自然的方式重新排列代码以避免这些错误。
      • @Neil:我有。它不能原谅你。
      【解决方案6】:

      样式和可读性将引导我使用异常来处理错误情况。您的调用者如何知道您的方法是否被正确调用?

      也就是说:goto 不应该用于此; return 是更好的选择。如果您需要在例程结束时执行任何操作,无论它为何退出 - 那么您应该抛出异常并使用 catch 块。

      【讨论】:

        【解决方案7】:

        return 一般优先于goto;通常,诋毁标签的人无法提出任何实际论据来支持他们的教条。1

        但是,您的情况更为明确:您为什么选择goto?两种解决方案是等效的,只是goto 要求您在函数结束之前写end:,占用空间并且无缘无故地看起来很丑。

        因此,在提供的两个选项中,我推荐returns。也就是说,我还建议认真考虑在回答这个问题时给出的其他建议(例外情况,并限制条件句的“嵌套性”)。


        1 或者,如果他们这样做,它通常类似于“goto 让你的对象挂起”,这是不正确的(根据 the C++0x FDIS 中的 6.6/2)。 goto 不会破坏 RAII 模型

        【讨论】:

        • +1 使用goto 有时是合理的,但在 C++ 中比在 C 中要少得多。我同意你对教条的看法。
        • @David:是的,当然没有很多合理的用例,而且我个人不确定我曾经使用过它。
        【解决方案8】:

        goto End; 很好,部分原因在于可读性,但也因为如果您稍后意识到在关闭函数之前需要发生一些事情,例如释放内存或释放资源,您可以集中该过程而不是复制粘贴它。

        【讨论】:

        • 不,在 C++ 中,释放资源的方式是在析构函数中,因此可以通过多个 return 语句轻松完成。
        • @jalf:使用 RAII 是一种选择。你没有必须使用它。无论如何,OP 说 - 明确 - 他没有资源可以释放。
        • 这就是为什么如果你最终会得到上述资源是件好事。这是我不久前添加到常用的一种模式,因为我发现我的自我复制粘贴了大量的小代码来处理这些情况,它消除了这个问题,并且比仅仅返回更具描述性;
        • @Tomalak:向自己的脚开枪也是一种选择。编写错误代码也是如此。但 RAII 是 good C++ code anno 2011 的一个非常基本的方面。它解决了唯一可能导致人们更喜欢 goto 而不是多个 return 语句的问题
        • @jalf: RAII 和goto 不是相互排斥的。事实上,它们使用的含义在很大程度上是正交的。 (但是,是的,当然应该尽可能使用“最佳实践”,撇开我的迂腐主义不谈:P)
        【解决方案9】:

        其实不建议使用goto。它会在代码的可读性方面产生问题。最好使用break; 语句,在您想离开循环并在代码末尾返回的地方。这是标准程序。

        while( get_input() )
        {
            if( input == "somethingcool" )
            {
                if( !process_somethingcool() )
                    break; // <-- a couple of these
            }
            //...
            else // bad input, error handling is fancier than this, but irrelevant to the question
                break; //although its better if you handle it somehow here.
        }
        return;
        

        【讨论】:

        • goto 的易读性与此用例中return 的易读性完全相同。停止重复毫无根据的教条。我也很想看到你的术语“标准程序”的引用。
        • 我很乐意看到您使用带有多个标签的多个 goto 并跟上这一点。但我不会建议任何人。 break 关键字存在是有原因的。
        • @Bibhas: ... 在 this 用例中。 为工作使用正确的工具,而不是一概而论。 break 与 OP 提出的使用 goto在此处完全相同,但您甚至不会因为使用它的可能性而退缩,这一事实非常有说服力。
        • 好吧,我看到你已经有使用goto-bashing 的经验了。所以我会克制自己不要去那里。所有的答案都在这里。让 OP 选择他想要的。还有一件事,即使我关注了你的最后评论,break 也是适合这里工作的工具(与它们完全相同)..
        • @Tomalak Geret'kal:不,gotobreak 在这里做的事情不同。 C++ 处理范围和对象生命周期的方式有一些细微的差别。 goto 违反了该模型,而 break 没有。
        【解决方案10】:

        为什么不使用“保护子句”(通过反转逻辑)而不是嵌套的 if 来构建您的代码。那么使用 goto 的整个问题就变得没有意义了。

        while( get_input() )
        {
            if( input != "somethingcool" )
            {
                return; //handle error
            }
            if( !process_somethingcool() )
                return; //handle error
            }
        }
        return; //success
        

        【讨论】:

        • 我不明白这是如何提出这个问题的。无论如何,摆脱嵌套的 if 是 +1。
        • 在我的例子中,这并没有摆脱嵌套的if。我认为您没有看到//...,它表示可以输入更多的字符串,从而使您的上述构造不正确。它确实没有回答我的问题。
        【解决方案11】:

        两者都没有。您应该将代码重构为可读的 并且可维护。我见过的每个案例(而且我见过很多) 程序员需要goto,大多数情况下 在他需要多次退货的情况下(以及在所有情况下 return 已经嵌套在一个循环中)会更好地解决 将代码重构为单独的、更简单的函数,每个函数 它在函数末尾有一个返回。 (多 对于非常简单的函数,返回有时是合理的;例如 如果函数中唯一的语句是 switch,每个 case 都以 return 结尾。)

        【讨论】:

          【解决方案12】:

          恕我直言,使用多个返回,不要使用 goto。使用 goto 的人,也会放火烧狗。

          【讨论】:

          • 真的是Hyperboreus吗?放火烧狗?
          • 就个人而言,我认为程序或 OO 语言中的 goto 语句仅是其祖先的遗产。在你的代码中乱跳并不会真正增加它的可读性和可验证性。
          • 我希望对此有所不同,它允许您在函数的顶级范围内和单个位置处理异常函数关闭。从而使可读性更容易。我并不是说我们应该放弃其他流控制来支持 goto,但在这种情况下,它是适合这项工作的工具。
          • @Mimisbrunnr。确认。而且毕竟也是个人喜好和选择的问题。
          • @Hyperboreus:抱歉,这根本不是真的。 goto 不是 longjmp;它确实违反了对象清理。请参阅 C++0x FDIS 中的 6.6/2。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-08-14
          • 2021-02-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-12
          • 2015-09-22
          相关资源
          最近更新 更多