在 Ruby 中对块使用. . end 和大括号

我有一个同事谁是积极地试图说服我,我不应该使用做。.在 Ruby 中定义多行块时使用大括号。

我坚定地支持只在短的一行代码中使用花括号。.结束一切。但我觉得我应该联系更大的社区来寻求解决办法。

那么到底是什么,为什么呢? (一些应该有代码的例子)

context do
setup { do_some_setup() }
should "do somthing" do
# some more code...
end
end

或者

context {
setup { do_some_setup() }
should("do somthing") {
# some more code...
}
}

就我个人而言,只要看看上面的答案为我的问题,但我想打开这个更大的社区。

55984 次浏览

说到个人偏好,我更喜欢大括号而不是 do/end 块,因为大多数后台语言使用大括号而不是 do/end 约定,所以对于更多的开发人员来说,大括号更容易理解。如果 do/end 被6/10的开发人员使用,那么所有人都应该使用它,如果6/10的开发人员使用花括号,那么就坚持这种模式。

这完全是关于创建一个模式,这样团队作为一个整体可以更快地识别代码结构。

它们之间有细微的区别,但是{}比 do/end 绑定得更紧密。

这里有一些观点,这实际上是一个个人偏好的问题。许多红宝石爱好者采用你的方法。但是,另外两种常见的样式是始终使用其中一种,或者对返回值的块使用 {},对为副作用执行的块使用 do ... end

我见过的最常见的规则(最近在 雄辩的露比中)是:

  • 如果它是一个多行块,那么使用 do/end
  • 如果是单行块,则使用{}

来自 编程 Ruby:

大括号的优先级较高; do 的优先级较低。如果方法调用的参数没有用括号括起来,那么块的大括号形式将绑定到最后一个参数,而不是整个调用。Do 表单将绑定到调用。

密码

f param {do_something()}

将块绑定到 param变量,而代码

f param do do_something() end

将块绑定到函数 f

然而,如果你把函数参数放在括号里,这就不成问题了。

一般惯例是用 do。.多行块的 end 和单行块的大括号,但是这两者之间也有区别,可以用下面的例子来说明:

puts [1,2,3].map{ |k| k+1 }
2
3
4
=> nil
puts [1,2,3].map do |k| k+1; end
#<Enumerator:0x0000010a06d140>
=> nil

这意味着{}的优先级高于 do。.最后,所以在决定使用什么时要记住这一点。

还有一个例子,在你发展自己的偏好时要牢记在心。

以下代码:

task :rake => pre_rake_task do
something
end

真正的意思是:

task(:rake => pre_rake_task){ something }

还有这个代码:

task :rake => pre_rake_task {
something
}

真正的意思是:

task :rake => (pre_rake_task { something })

因此,为了得到你想要的实际定义,使用大括号,你必须这样做:

task(:rake => pre_rake_task) {
something
}

也许对参数使用大括号是你想要做的事情,但是如果你不这样做,它可能是最好的使用。.在这些情况下结束,以避免这种混淆。

我投票赞成“做/结束”


惯例是多行程序使用 do .. end,一行程序使用 { ... }

但是我更喜欢 do .. end,所以当我只有一行代码时,我还是会使用 do .. end,但是像往常一样将 do/end 格式化为三行。这样大家都高兴。

  10.times do
puts ...
end

{ }的一个问题是它是诗歌模式敌对的(因为它们紧密地绑定到最后一个参数,而不是整个方法调用,所以必须包括方法括号) ,而且在我看来,它们看起来不那么好。它们不是语句组,并且为了可读性与散列常量发生冲突。

另外,我在 C 程序中已经看够了 { }。露比的方式,像往常一样,更好。if块只有一种类型,而且您永远不必返回并将语句转换为复合语句。

实际上,这是我个人的偏好,但话说回来,在过去3年的红宝石经历中,我学到的是红宝石有它自己的风格。

一个例子是,如果您来自一个 JAVA 背景,那么您可能会使用一个布尔方法

def isExpired
#some code
end

注意驼峰大小写,最常见的是‘ is’前缀,将其标识为布尔方法。

但是在红宝石的世界里,同样的方法

def expired?
#code
end

所以我个人认为,最好用“ Ruby way”(但我知道这需要一些时间来理解(我花了大约1年时间: D))。

最后,我会和你一起去

do
#code
end

积木。

一对有影响力的红宝石学家建议在使用返回值时使用大括号,在不使用返回值时使用/结束。

Http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (在 archive. org 上)

Http://onestepback.org/index.cgi/tech/ruby/bracevsdoend.rdoc (在 archive. org 上)

总的来说,这似乎是一种很好的做法。

我想稍微修改一下这个原则,说明您应该避免在单行中使用 do/end,因为它更难阅读。

使用大括号必须更加小心,因为它会绑定到方法的最终参数,而不是整个方法调用。只要加上括号就可以避免这种情况。

大括号有一个主要的好处——许多编辑器更容易匹配它们,使某些类型的调试更容易。同时,关键字“ do... end”很难匹配,特别是因为“ end”也匹配“ if”。

我提出了另一个答案,虽然 很大的差异已经被指出(优先级/绑定) ,这可能会导致很难找到问题(锡人,和其他人指出)。 我认为我的例子展示了一个不太常见的代码片段的问题,即使是有经验的程序员也不会像星期天的时间那样阅读:

module I18n
extend Module.new {
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
}
end


module InplaceTrans
extend Module.new {
def translate(old_translate, *args)
Translator.new.translate(old_translate, *args)
end
}
end

然后我做了一些代码美化..。

#this code is wrong!
#just made it 'better looking'
module I18n
extend Module.new do
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
end
end

如果你把这里的 {}改成 do/end你会得到错误,那个方法 translate不存在..。

为什么会发生这种情况在这里指出了不止一个优先级。但是在哪里放支架呢?(@铁皮人: 我总是用牙套,像你一样,但在这里... 监督)

所以每个回答都是

If it's a multi-line block, use do/end
If it's a single line block, use {}

只是 错了,如果没有使用“但是要注意大括号/优先级!”

再说一遍:

extend Module.new {} evolves to extend(Module.new {})

还有

extend Module.new do/end evolves to extend(Module.new) do/end

(不管扩展的结果对块有什么影响...)

因此,如果你想使用 do/end,请使用:

#this code is ok!
#just made it 'better looking'?
module I18n
extend(Module.new do
old_translate=I18n.method(:translate)
define_method(:translate) do |*args|
InplaceTrans.translate(old_translate, *args)
end
alias :t :translate
end)
end

我个人的风格是强调可读性超过严格的规则的 {... }do... end的选择,当这样的选择是可能的。我对可读性的看法如下:

[ 1, 2, 3 ].map { |e| e + 1 }      # preferred
[ 1, 2, 3 ].map do |e| e + 1 end   # acceptable


[ 1, 2, 3 ].each_with_object [] do |e, o| o << e + 1 end # preferred, reads like a sentence
[ 1, 2, 3 ].each_with_object( [] ) { |e, o| o << e + 1 } # parens make it less readable


Foo = Module.new do     # preferred for a multiline block, other things being equal
include Comparable
end


Foo = Module.new {      # less preferred
include Comparable
}


Foo = Module.new { include Comparable }      # preferred for a oneliner
Foo = module.new do include Comparable end   # imo less readable for a oneliner


[ [ 1 ], [ 1, 2 ] ].map { |e| e.map do |e| e + 1 end }  # slightly better
[ [ 1 ], [ 1, 2 ] ].map { |e| e.map { |e| e + 1 } }     # slightly worse

在更复杂的语法中,比如多行嵌套块中,我尝试在大多数自然结果中使用 {... }do... end分隔符,例如。

Foo = Module.new {
if true then
Bar = Module.new {                          # I intersperse {} and keyword delimiters
def quux
"quux".tap do |string|                  # I choose not to intersperse here, because
puts "(#{string.size} characters)"    # for multiline tap, do ... end in this
end                                     # case still loks more readable to me.
end
}
end
}

虽然缺乏严格的规则可能会为不同的程序员产生略有不同的选择,但我相信,对可读性的逐案优化,尽管是主观的,但是比遵守严格的规则更有好处。

还有第三种选择: 编写一个预处理程序,从缩进中在自己的行中推断出“ end”。喜欢简洁代码的深度思考者碰巧是正确的。

更好的是,黑掉 Ruby 这就是一面旗子。

当然,“挑起你的战争”最简单的解决方案是采用风格约定,即一系列的结束都出现在同一行上,并教你的语法着色来减弱它们。为了便于编辑,可以使用编辑器脚本来展开/折叠这些序列。

我的红宝石代码中有20% 到25% 的行在它们自己的行上是“ end”的,所有这些都是通过缩进约定推断出来的。Ruby 是类 Lisp 语言中取得最大成功的语言。当人们争论这个问题时,问他们那些可怕的括号在哪里,我给他们展示了一个以七行多余的“ end”结尾的红宝石函数。

多年来,我一直使用 lisp 预处理器来推断大多数括号: 一个“ |”打开一个在行尾自动关闭的组,一个美元符号“ $”充当一个空的占位符,否则就没有符号来帮助推断这些组。这当然是宗教战争的领土。没有括号的 Lisp/scheme 是所有语言中最富诗意的。不过,使用语法着色更容易使括号变得安静。

我仍然使用 Haskell 的预处理器编写代码,以添加 herdocs,并将所有刷新行默认为注释,所有内容都缩进为代码。我不喜欢评论人物,不管是什么语言。

我举了一个投票最多的例子,比如,

[1,2,3].map do
something
end

如果检查 array.rb 和 Map 方法的头部说明中的内部实现:

Invokes the given block once for each element of self.

def map(*several_variants)
(yield to_enum.next).to_enum.to_a
end

也就是说,它接受一个代码块-无论是在 结束之间的是作为产量执行。然后结果将再次以数组的形式收集,因此它将返回一个全新的对象。

因此,无论何时遇到 结束块或 { },只要有一个 思维导图,代码块作为参数传递,其中 将在内部被处决。