do..end vs大括号的ruby块

我有一个同事积极地说服我,我不应该使用do..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... } } 

就个人而言,只要看看上面这个问题,我就可以回答这个问题,但是我想把这个问题向更大的社区开放。

一般惯例是使用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 

这意味着{}的优先级高于..end,因此请在决定要使用的内容时记住这一点。

PS:另一个例子要记住,而你发展你的喜好。

以下代码:

 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在这些情况下,以避免这种混淆。

从编程Ruby :

大括号具有高优先级; 具有低优先级。 如果方法调用的参数不包含在圆括号中,块的括号forms将绑定到最后一个参数,而不是整个调用。 do表单将绑定到调用。

所以代码

 f param {do_something()} 

在代码中将块绑定到paramvariables

 f param do do_something() end 

将该块绑定到函数f

但是,如果将括号中的函数参数括起来,则这不是问题。

对此有几点看法,这实际上是个人喜好的问题。 许多ruby主义者采取你的做法。 但是,其他两种常见的样式是始终使用一种或另一种,或者对于返回值的块使用{}对于为副作用执行的块do ... end

花括号有一个主要的好处 – 许多编辑器比较容易匹配它们,使得某些types的debugging更容易。 同时,关键词“do … end”比较难以匹配,特别是因为“end”也匹配“if”。

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

  • 如果是多行块,请使用do / end
  • 如果是单行块,请使用{}

我正在投票做/结束


公约是do .. end for multiline和{ ... } for one-liners。

但是我喜欢做得更好,所以当我有一个class轮的时候,我使用do .. end ,但是像往常一样格式化,以三行结束。 这让每个人都开心。

  10.times do puts ... end 

{ }一个问题是它是诗歌模式敌对的(因为它们紧紧地绑定到最后一个参数而不是整个方法调用,所以你必须包含方法parens),而且在我看来,它们看起来并不像不错。 他们不是语句组,他们与哈希常量冲突为了可读性。

另外,我在C程序中看到了足够多的{ } 。 像往常一样,Ruby的方式更好。 只有一种types的if块,你不必返回并将语句转换为复合语句。

一些有影响力的ruby主义者build议在使用返回值时使用大括号,如果不使用则使用大括号。

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惯例中使用它们,所以我更喜欢在do / end块上使用大括号,因为对于更多的开发人员来说它更容易理解。 有人说,真正的关键是要在你的店里达成协议,如果6/10开发者使用do / end,而不是每个人都应该使用它,如果6/10使用大括号,那么坚持这个范例。

它的一切都是为了让整个团队可以更快地识别代码结构。

它们之间有一个微妙的区别,但{}比起来更紧密。

其实这是个人的喜好,但是说了这么多,我过去三年的宝贵经验,我所学到的就是ruby有它的风格。

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

 def isExpired #some code end 

注意骆驼的情况,最经常的“是”前缀,以确定它是一个布尔方法。

但在ruby世界,同样的方法将是

 def expired? #code end 

所以我个人认为,最好是用“ruby的方式”(但是我知道需要一些时间才能理解(我花了大约1年的时间:D))。

最后,我会去

 do #code end 

块。

我又提了一个答案,虽然已经指出了很大的差别(优先/有约束力),而且会导致很难发现问题(“铁皮人”等人指出了这个问题)。 我想我的例子显示了一个不太常见的代码片段的问题,即使有经验的程序员也不会像星期日那样读取:

 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 

我的个人风格是要强调可读性,即在可能的情况下select{}与… do endselect。 我的可读性理念如下:

 [ 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 } 

尽pipe缺乏严格的规则可能会给不同的程序员带来不同的select,但我认为,逐个可读性的优化虽然是主观的,但却是对严格规则的净收益。

还有第三种select:编写一个预处理器,从缩进中自行推断“end”。 谁更喜欢简洁的代码深刻的思想家恰好是正确的。

更好的是,破解ruby,所以这是一个标志。

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

我的ruby代码行中的20%到25%是“结束”在他们自己的行上,都是由我的缩进公约推断的。 Ruby是类似Lisp的语言,取得了最大的成功。 当人们争论这个问题时,我问他们在哪里是可怕的括号,我给他们展示了一个以七行多余的“end”结尾的ruby函数。

我多年来使用lisp预处理器来推断大多数圆括号:bar'|' 开了一个在行末自闭的组,一个美元符号'$'作为一个空的占位符,否则就没有符号来帮助推断分组。 这当然是宗教战争的领土。 没有括号的Lisp / scheme是所有语言中最具诗意的。 尽pipe如此,使用语法着色来简化括号却更容易。

我仍然使用Haskell的预处理器进行编码,添加heredocs,并将所有刷新行默认为注释,所有内容缩进为代码。 我不喜欢评论字符,不pipe语言。