ChatGPT解决这个技术问题 Extra ChatGPT

在 Ruby 中显式返回是一种好的风格吗?

来自 Python 背景,在风格方面总是有一种“正确的方式”(一种“Pythonic”方式),我想知道 Ruby 是否也存在同样的情况。我一直在使用自己的风格指南,但我正在考虑发布我的源代码,并且我希望它遵守任何可能存在的不成文规则。

在方法中显式输入 return 是“Ruby 方式”吗?我已经看到它有没有完成,但是有正确的方法吗?是否有合适的时间去做?例如:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
这是一个很老的问题,但是对于那些有类似问题的人,强烈推荐这本书 Eloquent Ruby。它与其说是一本风格指南,不如说是对编写惯用 Ruby 的介绍。我希望更多的人阅读它!
作为继承了使用 Ruby 开发的大型遗留应用程序的人,我非常感谢您提出这个问题。这值得一票。这在我们的旧版应用程序中随处可见。我在这个团队中的一项工作是改进代码库(对于 Ruby 新手来说很难)。

E
EliadL

旧的(和“已回答的”)问题,但我会投入两分钱作为答案。

TL;DR - 您不必这样做,但在某些情况下它可以使您的代码更加清晰。

尽管不使用显式返回可能是“Ruby 方式”,但对于使用不熟悉代码或不熟悉 Ruby 的此功能的程序员来说,这会让人感到困惑。

这是一个有点人为的例子,但是想象一下有一个像这样的小函数,它在传递的数字上加一,并将它分配给一个实例变量。

def plus_one_to_y(x)
    @y = x + 1
end

这是否意味着是一个返回值的函数?很难说开发人员的意思是什么,因为它既分配了实例变量,又返回了分配的值。

假设很久以后,另一个程序员(可能不太熟悉 Ruby 如何根据执行的最后一行代码返回结果)出现并想要放入一些打印语句进行日志记录,并且函数变成了这个......

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

现在,如果有任何东西需要返回值,该函数就会被破坏。如果没有任何东西期望返回值,那很好。很明显,如果在代码链的某个地方,调用它的东西期望返回值,它将失败,因为它没有得到预期的结果。

现在真正的问题是:真的有什么期望返回值吗?这是否破坏了某些东西?它会在未来破坏一些东西吗?谁知道!只有对所有调用进行完整的代码审查才能让您知道。

所以至少对我来说,最佳实践方法是要么非常明确地表明你正在返回一些重要的东西,要么在它不重要时什么都不返回。

所以在我们的小演示函数的情况下,假设我们希望它返回一个值,它会这样写......

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

任何程序员都非常清楚它确实返回了一个值,而且他们更难在没有意识到的情况下破坏它。

或者,可以这样写,省略 return 语句......

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

但是为什么要省去返回这个词呢?为什么不把它放在那里,让它 100% 清楚发生了什么?它实际上不会影响您的代码的执行能力。


有趣的一点。我想当我问这个问题时,我实际上遇到了类似的情况,原始开发人员在他们的代码中使用了隐式返回值,很难理解发生了什么。感谢您的回答!
我在谷歌搜索隐式回报时发现了这个问题,因为我刚刚被这个烧伤了。我在隐式返回某些内容的函数的末尾添加了一些逻辑。大多数对它的调用并不关心(或检查)返回的值,但有一个调用 - 它失败了。幸运的是,至少它出现在一些功能测试中。
虽然确实如此,但代码的可读性很重要,但在我看来,避开编程语言的一个众所周知的特性来支持冗长是不好的做法。如果开发人员不熟悉 Ruby,那么也许他们应该在接触代码之前先熟悉一下。我发现为了某些非 Ruby 开发人员以后必须做的事情而不得不增加我的代码的混乱有点愚蠢。我们不应该将一种语言简化为最低公分母。 Ruby 的部分美妙之处在于,我们不必使用 5 行代码来表达应该只需要 3 行的代码。
如果单词 return 为代码添加了语义,为什么不呢?它几乎不是很冗长——我们不是在说 5 行对 3 行,只是多一个词。我希望至少在函数中(可能不在块中)看到 return,以便表达代码的实际作用。有时您必须返回 mid-function - 不要仅仅因为可以而忽略最后一行的最后一个 return 关键字。我根本不喜欢冗长,但我喜欢一致性。
这是一个很好的答案。但它仍然存在风险,即您编写一个打算成为一个过程(也称为函数返回单元)的 Ruby 方法,例如 side_effect() 并且另一个开发人员决定(ab)使用您的过程的隐式返回值。要解决此问题,您可以明确地使您的过程 return nil
M
Miguel

不会。好的 Ruby 风格通常只使用显式返回来提前返回。 Ruby 擅长代码极简主义/隐式魔法。

也就是说,如果显式返回会使事情变得更清晰或更易于阅读,那么它不会损害任何东西。


如果代码极简主义导致混淆极简主义,那么它的意义何在?
@JonCrowell 它应该导致文档最大化。
@jazzpi 如果您必须大量记录您的代码才能很好地理解它,那么您不是在练习极简主义,而是在练习 code golf
排除最终返回并不令人困惑,但包含它却令人困惑 - 作为 Ruby 开发人员,return 作为函数的提前退出已经具有重要的语义含义。
@DevonParsons 到底怎么会有人将最后一行的 return 混淆为 EARLY EXIT 呢?
J
Jörg W Mittag

我个人使用 return 关键字来区分我所说的函数方法,即主要为返回值执行的方法和主要为执行的过程方法他们的副作用。因此,返回值很重要的方法,需要额外的 return 关键字来引起对返回值的注意。

我在调用方法时使用相同的区别:函数式方法有括号,而过程式方法没有。

最后但同样重要的是,我还将这种区别与块一起使用:功能块得到花括号,程序块(即“做”某事的块)得到 do/end

但是,我尽量不要对此深信不疑:对于块、花括号和 do/end 具有不同的优先级,我没有添加显式括号来消除表达式的歧义,而是切换到另一种样式。方法调用也是如此:如果在参数列表周围添加括号使代码更具可读性,我会这样做,即使所讨论的方法本质上是程序性的。


现在,我在任何“单行”中都省略了返回,这与您正在做的事情相似,其他所有事情我都做同样的事情。很高兴知道我并没有完全不喜欢我的风格。 :)
@Jorg:您在代码中使用早期回报吗?这是否会对您的程序/功能方案造成任何混淆?
@Andrew Grimm:是的,是的。我正在考虑反转它。我的绝大多数方法都是引用透明的/纯的/功能的/无论如何你想调用它,所以在那里使用较短的形式是有意义的。并且以函数式编写的方法往往没有早期返回,因为这暗示了“步骤”的概念,这不是很实用。虽然,我不喜欢中间的回报。我要么在开始时将它们用作警卫,要么在结束时使用它们来简化控制流程。
很好的答案,但实际上使用 () 调用过程方法和不使用函数方法是更好的做法 - 请参阅下面的答案以了解原因
A
Alex Dean

实际上,重要的是要区分:

函数 - 为其返回值执行的方法 过程 - 为其副作用执行的方法

Ruby 没有本地方式来区分这些 - 这使您容易编写过程 side_effect() 和另一个开发人员决定滥用您的过程的隐式返回值(基本上将其视为不纯函数)。

要解决此问题,请参考 Scala 和 Haskell 的书,并让您的过程显式返回 nil(在其他语言中也称为 Unit())。

如果您遵循这一点,那么是否使用显式 return 语法只是个人风格的问题。

进一步区分函数和过程:

复制 Jörg W Mittag 用花括号编写功能块和使用 do/end 编写程序块的好主意 调用过程时,使用 (),而调用函数时,不要

请注意,Jörg W Mittag 实际上提倡相反的方式 - 避免对过程使用 () - 但这是不可取的,因为您希望将副作用方法调用与变量清楚地区分开来,尤其是当 arity 为 0 时。请参阅 Scala style guide on method invocation 了解细节。


如果我需要 function_a 的返回值,而 function_a 被写入调用(并且只能通过调用)procedure_b,那么 function_a 真的是一个函数吗?它返回一个值,满足您对功能的要求,但它有一个副作用,满足程序的要求。
你是对的 Devon - function_a 可以包含 procedure_... 调用树,实际上 procedure_a 可以包含 function_... 调用树。与 Scala 类似,但与 Haskell 不同,Ruby 无法保证函数没有副作用。
n
ndnenkov

The style guide 声明,您不应在最后一条语句中使用 return。您仍然可以使用它if it's not the last one。这是社区严格遵守的约定之一,如果您打算与任何使用 Ruby 的人合作,您也应该如此。

话虽如此,使用显式 return 的主要论据是它会让来自其他语言的人感到困惑。

首先,这并不完全是 Ruby 独有的。例如 Perl 也有隐式返回。

其次,这适用于大多数人来自 Algol 语言。其中大多数都比 Ruby“低级”,因此您必须编写更多代码才能完成任务。

java 中方法长度(不包括 getter/setter)的常见启发式方法是一个屏幕。在这种情况下,您可能看不到方法定义和/或已经忘记了您从哪里返回。

另一方面,在 Ruby 中,最好坚持使用方法 less than 10 lines long。鉴于此,人们会想知道为什么他必须多写约 10% 的陈述,而这些陈述是明确暗示的。

由于 Ruby 没有 void 方法,而且一切都更加简洁,因此如果使用显式 return,则只会增加开销而没有任何好处。


“这是社区严格遵守的约定之一” 我的经验是,不,人们不会严格遵守这一点,除非他们是非常有经验的 Ruby 开发人员。
@TimHolt 那时我一定很幸运。 (:
@ndn 完全同意。重要的是,当一个方法(或类)开始超过 5/100 行时,重新考虑您要完成的工作是个好主意。真正的 Sandi 规则是一种用于思考代码而不是任意教条的认知框架。我遇到的大多数代码都没有问题,因为它是人为限制的——通常情况相反——具有多种职责的类,方法比许多类长。本质上太多的人“跳过鲨鱼”——混合责任。
有时约定仍然是一个坏主意。它是一种依赖于魔法的风格约定,使得返回类型难以猜测。除了节省 7 次击键之外没有任何优势,并且与几乎所有其他语言不一致。如果 Ruby 曾经做过 Python 3 风格的整合和简化,我希望隐含的任何东西都是第一个在砧板上。
除了很明显它实际上并没有使事情更容易阅读!魔术和可理解性不是一回事。
D
Devon Parsons

我同意 Ben Hughes 的观点,但不同意 Tim Holt 的观点,因为这个问题提到了 Python 的明确方式,并询问 Ruby 是否有类似的标准。

It does.

这是该语言的一个众所周知的特性,任何希望在 ruby 中调试问题的人都应该合理地期望知道它。


我在理论上同意你的观点,但在实践中,程序员会犯错误并搞砸。举个例子,我们都知道你在条件句中使用“==”而不是“=”,但我们之前都犯过这个错误,直到后来才意识到。
@TimHolt 我不是在解决任何特定的用例,我只是在回答这个问题。根据社区的选择,在不必要时使用 return 关键字显然是一种糟糕的风格。
很公平。我是说,如果您不小心,您提到的样式可能会导致错误。根据我的经验,我个人主张不同的风格。
@TimHolt 顺便说一句,这个问题是在编写风格指南之前提出的,所以不同意的答案不一定是错误的,只是过时了。我不知何故在这里偶然发现并没有看到我提供的链接,所以我想其他人也可能会在这里结束。
它不是官方的“标准”。 rubocop 使用的 ruby 样式指南并在上面与 Devon Parsons 的声明链接,是由非核心 ruby 黑客创建的。所以德文的评论实际上是完全错误的。当然,您仍然可以使用它,但它不是标准。
P
Praveen Angyan

这是您最喜欢哪种风格的问题。如果要从方法中间的某个位置返回,则必须使用关键字 return。


G
Gishu

正如本所说。 “ruby 方法的返回值是函数体中最后一条语句的返回值”这一事实导致 return 关键字在大多数 ruby 方法中很少使用。

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end

如果您的意图仍然明确,您也可以写“return nil if failed_some_early_check”
@Gishu我实际上是在抱怨 if 语句,而不是方法名称。
@Andrew ..哦,失踪的结局:)。知道了。修复了迈克的其他评论。
为什么不if failed_check then nil else @list end?这是真正的功能
@cdunn2001 不清楚为什么 if then else 是有效的。这种风格的原因是它通过早期退出减少了嵌套。恕我直言也更具可读性。