为什么一个功能应该只有一个出口点?[关闭]


97

我一直都听说过单个出口点函数是一种不好的编码方式,因为这样会降低可读性和效率。我从未听说过有人在争论另一面。

我认为这与CS有关,但是这个问题在cstheory stackexchange上被否决了。



6
答案是没有永远正确的答案。我经常发现使用多个退出代码更容易。我还发现(在更新上面的代码时)由于相同的多个出口,修改/扩展代码更加困难。根据具体情况做出这些决定是我们的工作。当一个决定总是有一个“最佳”答案时,就不需要我们了。
JS。

1
@finnw法西斯MODS已经删除了最后两个问题,以确保他们将不得不再次回答,又一次,又一次
马腾Bodewes

尽管问题中有“争论”一词,但我真的不认为这是一个基于观点的问题。这与良好的设计等息息相关。我认为没有理由将其关闭,但有理由。
Ungeheuer

1
单个出口点简化了调试,读取,性能测量和调整,重构的过程,这是客观且实质性的。使用早期返回(在简单的参数检查之后)可以合理地将两种样式融合在一起。考虑到单个出口点的好处,用返回值乱扔代码只是一个懒惰,草率,粗心的程序员的证据-至少可能不喜欢小狗。
里克·奥谢

Answers:


108

有不同的思想流派,很大程度上取决于个人喜好。

一个是如果只有一个出口点,那么它就不会那么混乱-您在方法中只有一条路径,并且知道在哪里寻找出口。在不利的一面,如果您使用缩进来表示嵌套,则您的代码最终将大量缩进到右侧,并且很难遵循所有嵌套的作用域。

另一个是您可以检查先决条件并在方法开始时提早退出,这样您就可以在方法主体中知道某些条件是正确的,而整个方法主体都不会向右缩进5英里。通常,这可以最大程度地减少您需要担心的作用域数量,从而使代码易于遵循。

第三个是您可以随心所欲地退出。在过去,这曾经更加令人困惑,但是现在我们有了语法着色的编辑器和编译器可以检测无法访问的代码,因此处理起来要容易得多。

我正好在中间营地。强制单个出口点是毫无意义甚至适得其反的限制,恕我直言,尽管整个方法中的随机出口有时会导致混乱而难以遵循的逻辑,在逻辑上很难确定给定的代码是否会被执行。但是“控制”您的方法可以显着简化方法的主体。


1
singe exit通过执行go to语句,可以避免范例中的深层嵌套。另外,有机会在函数的本地Error标签下执行某些后处理,而对于多个returns 是不可能的。
Ant_222

2
通常有一个很好的解决方案,可以避免去尝试。我更喜欢'return(Fail(...))'并将共享的清理代码放入Fail方法中。这可能需要传递一些本地变量以释放内存等,但是除非您的代码对性能至关重要,否则通常比转到IMO更干净的解决方案。它还允许几种方法共享相似的清理代码。
杰森·威廉姆斯

有一种基于客观标准的最佳方法,但是我们可以同意存在各种思想流派(正确和错误),并且确实归结为个人偏爱(偏爱或反对正确方法)。
里克·奥谢

39

我的一般建议是,在可行的情况下,return语句应位于具有任何副作用的第一个代码之前,或位于具有任何副作用的最后一个代码之后。我会考虑类似:

  if(!argument)//检查是否为非null
    返回ERR_NULL_ARGUMENT;
  ...处理非空参数
  如果(确定)
    返回0;
  其他
    返回ERR_NOT_OK;

比:

  int return_value;
  if(argument)//非null
  {
    ..处理非空参数
    ..适当设置结果
  }
  其他
    结果= ERR_NULL_ARGUMENT;
  返回结果;

如果某个条件应阻止某个功能执行任何操作,则我更喜欢在该功能可以执行任何操作的位置上方的某个位置提前退出该功能。但是,一旦函数执行了带有副作用的操作,我更愿意从底层开始,以明确必须处理所有的副作用。


您的第一个示例,管理ok变量,对我来说就像是单向返回方法。此外,if-else块可以简单地重写为:return ok ? 0 : ERR_NOT_OK;
Melebius

2
第一个示例return在执行所有操作的所有代码之前都有一个开头。至于使用?:运算符,将其写在单独的行上使许多IDE可以更轻松地将调试断点附加到不正常的情况。顺便说一句,“单个出口点” 的真正关键在于理解重要的是,对于每个对正常函数的特定调用,出口点是紧接在调用之后的点。如今的程序员认为这是理所当然的,但事情并非总是如此。在某些极少数情况下,代码可能必须没有堆栈空间才能通过,从而导致函数...
supercat

...通过条件或计算的goto退出。通常,任何具有足够资源以汇编语言以外的任何方式进行编程的东西都将能够支持堆栈,但是我编写的汇编代码必须在某些非常严格的约束下工作(在一种情况下,RAM的字节数应为零),在这种情况下,拥有多个出口点可能会有所帮助。
超级猫

1
所谓的“更清晰的例子”不太清楚,很难阅读。一个出口点始终易于阅读,易于维护,易于调试。
GuidoG '16

8
@GuidoG:根据省略部分中显示的内容,两种模式都可能更易读。使用“ return x;” 清楚地表明,如果到达该语句,则返回值为x。使用“结果= x;” 在返回结果之前,还有其他可能改变结果的可能性。如果实际上有必要更改结果,那么这将很有用,但是检查代码的程序员必须检查它以查看结果可能如何更改,即使答案是“不可能”。
超级猫

15

单一入口和出口点是结构化编程与分步Spaghetti编码的原始概念。人们认为,多个出口点函数需要更多代码,因为您必须适当清理分配给变量的内存空间。考虑一种情况,其中函数分配变量(资源),并且过早地退出该函数并且没有适当的清理会导致资源泄漏。另外,在每个出口之前构造清理将创建大量冗余代码。


RAII确实不是问题
BigSandwich

14

对于大多数事情,这取决于交付成果的需求。在“过去”中,带有多个返回点的意大利面条式代码会导致内存泄漏,因为偏爱该方法的编码人员通常无法很好地清理代码。在从嵌套作用域返回的情况下,当在返回过程中弹出堆栈时,某些编译器也会“丢失”对返回变量的引用,这也存在问题。更普遍的问题是可重入代码之一,该代码试图使函数的调用状态与其返回状态完全相同。oop的更改者违反了此规定,因此该概念被搁置了。

有可交付成果,最著名的是内核,它们需要多个出口点提供的速度。这些环境通常具有自己的内存和流程管理,因此将泄漏的风险降到最低。

就个人而言,我喜欢有一个退出点,因为我经常使用它在return语句上插入一个断点,并对代码如何确定该解决方案进行代码检查。我可以直接进入入口并逐步进行,这与大量嵌套和递归的解决方案有关。作为代码审阅者,一个函数的多次返回需要更深入的分析-因此,如果这样做是为了加快实现速度,那么您就在抢Peter救了Paul。代码审查将需要更多时间,从而使有效实现的假设无效。

-2美分

请参阅此文档以获取更多详细信息:NISTIR 5459


8
multiple returns in a function requires a much deeper analysis仅当功能已经非常强大(> 1个屏幕)时,否则它使分析变得更容易
dss539 2013年

2
多重收益永远不会使分析变得容易,只是相反
GuidoG

1
链接无效(404)。
fusi 2016年

1
@fusi-在archive.org上找到了它,并在这里更新了链接
sscheider

4

我认为,仅在某一点退出功能(或其他控制结构)的建议经常被超卖。通常只在两个点退出的原因有两个:

  1. 单出口代码据说更易于阅读和调试。(我承认我对此并不是很在意,但是已经给出了。单入口代码实际上更容易阅读和调试。)
  2. 单一退出代码链接并返回得更干净。

第二个原因是微妙的,并且具有一定的优点,特别是如果函数返回大型数据结构时。但是,除了...之外,我不必担心太多。

如果是学生,您希望在全班获得最高分。做老师喜欢的事情。从他的角度来看,他可能有充分的理由。因此,至少,您将了解他的观点。这本身具有价值。

祝好运。


4

我曾经提倡单出口风格。我的推理主要来自痛苦。

单出口更易于调试。

考虑到我们今天拥有的技术和工具,采取这种方法要合理得多,因为单元测试和日志记录可以使单次退出变得不必要。也就是说,当您需要监视代码在调试器中执行时,要理解和使用包含多个出口点的代码要困难得多。

当您需要插入分配以检查状态时(尤其是在现代调试器中用watch表达式替换),情况尤其如此。以隐藏问题或完全破坏执行的方式更改控制流也太容易了。

单次退出方法更容易在调试器中逐步执行,并且更容易取笑而不会破坏逻辑。


0

答案是非常依赖于上下文的。如果要制作GUI并具有初始化API并在主程序开始处打开窗口的功能,则将充满可能会引发错误的调用,每个调用都会导致程序实例关闭。如果您使用嵌套的IF语句并缩进,则代码可能很快会向右歪斜。在每个阶段都返回一个错误可能会更好,并且实际上更具可读性,同时使用代码中的几个标志进行调试也很容易。

但是,如果您要测试不同的条件并根据方法中的结果返回不同的值,则最好使用单个出口点。我曾经在MATLAB中处理图像处理脚本,但可能会变得非常庞大。多个出口点可能会使代码极难遵循。Switch语句更合适。

最好的办法就是随手学习。如果您正在编写代码,请尝试查找其他人的代码并查看他们如何实现。确定您喜欢哪些位,不喜欢哪些位。


-5

如果您觉得一个函数中需要多个出口点,则该函数太大且作用太大。

我建议阅读罗伯特·C·马丁(Robert C. Martin)的书《清洁代码》中有关函数的章节。

本质上,您应该尝试使用4行或更少的代码编写函数。

Mike Long博客的一些注释:

  • 功能的第一法则:它们应该很小
  • 第二条功能规则:它们应小于
  • if语句,while语句,for循环等中的块应为一行长
  • …那行代码通常是一个函数调用
  • 压痕不应超过一个或两个级别
  • 函数应该做一件事
  • 函数语句都应该处于相同的抽象级别
  • 一个函数的参数不能超过3个
  • 输出参数是代码气味
  • 将布尔标志传递给函数确实很糟糕。根据定义,您正在函数中执行两项操作。
  • 副作用是谎言。

29
4行?您编写了什么代码来使您如此简单?我真的怀疑例如Linux内核或git会这样做。
Shinzou

7
“将布尔标志传递给函数确实很糟糕。按照定义,您正在函数中做两件事。” 根据定义?不,该布尔值仅可能影响您的四行之一。同样,尽管我同意保持函数的大小很小,但四个限制太过严格了。这应被视为非常宽松的准则。
杰西

12
添加这样的限制将不可避免地使代码混乱。它更多的是关于方法的简洁明了,并坚持只执行它们打算做的事情,而没有不必要的副作用。
杰西

10
我希望我可以多次投票否决SO的罕见答案之一。
史蒂文·兰德斯

8
不幸的是,这个答案做了很多事情,可能需要分解为其他多个部分-全部少于四行。
伊莱(Eli)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.