在 Ruby 中显式返回是一种好的风格吗?
Posted
技术标签:
【中文标题】在 Ruby 中显式返回是一种好的风格吗?【英文标题】:Is it good style to explicitly return in Ruby? 【发布时间】:2010-11-04 14:54:12 【问题描述】:来自 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 新手来说很难)。 【参考方案1】:旧的(和“已回答的”)问题,但我会扔掉两美分作为答案。
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
。【参考方案2】:
没有。良好的 Ruby 风格通常只会对提前返回使用显式返回。 Ruby 擅长代码极简主义/隐式魔法。
也就是说,如果显式返回会使事情更清晰或更易于阅读,那么它不会造成任何损害。
【讨论】:
如果代码极简主义导致混乱,极简主义有什么意义? @JonCrowell 它应该导致文档最大化。 @jazzpi 如果您必须大量记录您的代码以使其易于理解,那么您不是在练习极简主义,而是在练习 code golf。 排除最终返回并不令人困惑,但包含它却令人困惑 - 作为一名 Ruby 开发人员,return
作为函数的提前退出已经具有重要的语义意义。
@DevonParsons 到底怎么会有人将最后一行的return
混淆为提前退出?【参考方案3】:
我个人使用return
关键字来区分我所说的函数式方法,即主要为返回值执行的方法和过程方法主要是因为它们的副作用而执行。因此,对于返回值很重要的方法,需要额外添加一个 return
关键字来引起对返回值的注意。
我在调用方法时使用相同的区别:函数式方法有括号,过程式方法没有。
最后但并非最不重要的一点是,我还将这种区别用于块:功能块得到花括号,程序块(即“做”某事的块)得到do
/end
。
但是,我尽量不要对此持宗教态度:对于块,花括号和do
/end
具有不同的优先级,而不是添加显式括号来消除表达式的歧义,我只是切换到另一种样式。方法调用也是如此:如果在参数列表周围添加括号使代码更具可读性,我会这样做,即使所讨论的方法本质上是程序性的。
【讨论】:
现在我在任何“单行”中都省略了返回,这与您正在做的事情相似,其他所有事情我都做同样的事情。很高兴知道我并没有完全不喜欢我的风格。 :) @Jorg:您在代码中使用提前返回吗?这是否会对您的程序/功能方案造成任何混淆? @Andrew Grimm:是的,是的。我正在考虑反转它。我的绝大多数方法都是引用透明的/纯的/功能的/无论如何你想调用它,所以在那里使用较短的形式是有意义的。并且以函数式编写的方法往往没有早期返回,因为这暗示了“步骤”的概念,这不是很实用。虽然,我不喜欢中间的回报。我要么在开始时将它们用作警卫,要么在结束时使用它们来简化控制流程。 很好的答案,但实际上使用 () 调用过程方法和不使用函数方法是更好的做法 - 请参阅下面的答案了解原因【参考方案4】:其实重要的是要区分:
-
函数 - 为其返回值执行的方法
过程 - 为产生副作用而执行的方法
Ruby 没有本地方式来区分这些 - 这使您容易编写过程 side_effect()
和另一个开发人员决定滥用您的过程的隐式返回值(基本上将其视为不纯函数)。
要解决此问题,请参考 Scala 和 Haskell 的书,并让您的过程明确返回 nil
(在其他语言中也称为 Unit
或 ()
)。
如果您遵循这一点,那么是否使用明确的return
语法只是个人风格的问题。
进一步区分函数和过程:
-
复制 Jörg W Mittag 的好主意,即用花括号编写功能块,用
do/end
编写程序块
调用过程时,使用()
,而调用函数时,不要
请注意,Jörg W Mittag 实际上提倡相反 - 避免 ()
s 用于过程 - 但这是不可取的,因为您希望副作用方法调用与变量明显区分开来,特别是当 arity 为 0 时。请参阅 @ 987654321@了解详情。
【讨论】:
如果我需要function_a
的返回值,而function_a
被写入调用(并且只能通过调用操作)procedure_b
,那么function_a
真的是一个函数吗?它返回一个值,满足您对功能的要求,但它有一个副作用,满足程序的要求。
你是对的,德文郡 - function_a
可以包含 procedure_...
调用树,实际上procedure_a
可以包含function_...
调用树。与 Scala 类似,但与 Haskell 不同,Ruby 无法保证函数没有副作用。【参考方案5】:
The style guide 声明,您不应该在最后一条语句中使用return
。你仍然可以使用它if it's not the last one。这是社区严格遵守的约定之一,如果您打算与任何使用 Ruby 的人合作,您也应该遵守。
话虽如此,使用显式return
s 的主要论据是它会让来自其他语言的人感到困惑。
java 中方法长度(不包括 getter/setter)的常见启发式方法是 一个屏幕。在这种情况下,您可能看不到方法定义和/或已经忘记了您从哪里返回。
另一方面,在 Ruby 中,最好坚持使用方法 less than 10 lines long。鉴于此,人们会想知道为什么他必须多写约 10% 的陈述,而这些陈述是明确暗示的。
由于 Ruby 没有 void 方法,而且一切都更加简洁,如果您使用显式 return
s,您只会增加开销而没有任何好处。
【讨论】:
“这是社区严格遵守的约定之一” 我的经验是,不,除非他们是非常有经验的 Ruby 开发人员,否则人们不会严格遵守。 @TimHolt 那时我一定很幸运。 (: @ndn 完全同意。重要的是,当一个方法(或类)开始超过 5/100 行时,重新考虑您要完成的工作是个好主意。真正的 Sandi 规则是一种用于思考代码而不是任意教条的认知框架。我遇到的大多数代码都没有问题,因为它是人为限制的——通常情况相反——具有多种职责的类,方法比许多类长。本质上是太多人“跳槽”——混合责任。 有时约定仍然是个坏主意。它是一种依赖于魔法的风格约定,使得返回类型难以猜测。除了节省 7 次击键之外没有任何优势,并且与几乎所有其他语言不一致。如果 Ruby 曾经做过 Python 3 风格的整合和简化,我希望隐含的 anything 是第一个在砧板上。 除非很明显它实际上并没有使事情更容易阅读!魔术和可理解性不是一回事。【参考方案6】:我同意 Ben Hughes 的观点,但不同意 Tim Holt 的观点,因为这个问题提到了 Python 的明确方式,并询问 Ruby 是否有类似的标准。
It does.
这是该语言的一个众所周知的特性,任何希望在 ruby 中调试问题的人都应该合理地了解它。
【讨论】:
理论上我同意你的观点,但在实践中,程序员会犯错误并搞砸。举个例子,我们都知道您在条件句中使用“==”而不是“=”,但我们之前都犯过这个错误,直到后来才意识到。 @TimHolt 我不是在解决任何特定的用例,我只是在回答这个问题。社区选择在不必要时使用return
关键字显然是不好的风格。
很公平。我是说,如果您不小心,您提到的样式可能会导致错误。根据我的经验,我个人主张不同的风格。
@TimHolt 顺便说一句,这个问题是在编写样式指南之前提出的,所以不同意的答案不一定是错误的,只是已经过时了。我不知何故在这里偶然发现并没有看到我提供的链接,所以我想其他人也可能会在这里结束。
这不是官方的“标准”。 rubocop 使用的 ruby 样式指南并在上面与 Devon Parsons 的声明链接,是由非核心 ruby 黑客创建的。所以德文的评论实际上是完全错误的。当然你仍然可以使用它,但它不是一个标准。【参考方案7】:
这是您最喜欢哪种风格的问题。如果要从方法中间的某个位置返回,则必须使用关键字 return。
【讨论】:
【参考方案8】:就像本说的。 “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.. 哦,缺失的结尾:)。知道了。已修复以平息 Mike 的其他评论。 为什么不if failed_check then nil else @list end
?这是真正的功能。
@cdunn2001 不清楚为什么 if then else 是有效的。这种风格的原因是它通过提前退出减少了嵌套。恕我直言,也更具可读性。以上是关于在 Ruby 中显式返回是一种好的风格吗?的主要内容,如果未能解决你的问题,请参考以下文章