对于Ruby中的块,执行。对比花括号

时间:2011-04-07 20:32:52

标签: ruby-on-rails ruby ruby-on-rails-3 coding-style

我有一位同事正在积极地试图说服我不要使用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...
  }
}

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

14 个答案:

答案 0 :(得分:224)

一般惯例是对单行块使用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..end,因此在决定要使用的内容时请记住这一点。

P.S:在制定偏好时要记住的另一个例子。

以下代码:

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来避免这种混淆。

答案 1 :(得分:49)

来自Programming Ruby

  

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

所以代码

f param {do_something()}

在代码

时将块绑定到param变量
f param do do_something() end

将块绑定到函数f

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

答案 2 :(得分:14)

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

答案 3 :(得分:8)

花括号有一个主要好处 - 许多编辑器可以更轻松地匹配它们,使某些类型的调试更容易。同时,关键字“do ... end”相当难以匹配,特别是因为“end”也匹配“if”s。

答案 4 :(得分:7)

我投票支持do / end


对于多行,惯例为do .. end,对于单行,惯例为{ ... }

但是我更喜欢do .. end,所以当我有一个班轮时,我仍然使用do .. end,但是像往常一样格式化三行中的do / end。这让每个人都开心。

  10.times do 
    puts ...
  end

{ }的一个问题是它是诗歌模式 - 敌对的(因为它们紧紧地绑定到最后一个参数而不是整个方法调用,所以你必须包括方法parens)而且它们只是,在我看来,看起来不太好看。它们不是语句组,它们与哈希常量冲突以便于阅读。

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

答案 5 :(得分:6)

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

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

答案 6 :(得分:5)

一些有影响力的rubyists建议在使用返回值时使用大括号,而在不使用返回值时使用大括号。

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,因为它更难阅读。

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

答案 7 :(得分:1)

归结为个人偏见,我更倾向于对do / end块进行花括号,因为大多数背景语言在do / end约定中使用它们,因此对更多开发人员更容易理解。有了这样说,真正的关键是在你的商店内达成协议,如果6/10开发人员使用/ end比每个人都应该使用它们,如果6/10使用花括号,那么坚持这个范例。

所有关于制作模式以便团队整体可以更快地识别代码结构。

答案 8 :(得分:1)

它们之间存在细微差别,但{}比do / end更紧密。

答案 9 :(得分:1)

实际上这是个人喜好,但话说回来,过去3年我的红宝石经历我所学到的是红宝石的风格。

一个例子是,如果你是从JAVA背景出来的,你可以使用布尔方法

def isExpired
  #some code
end 

注意驼峰情况,并且通常是“是”前缀,以将其标识为布尔方法。

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

def expired?
  #code
end

所以我个人认为,最好采用'红宝石方式'(但我知道需要一段时间才能理解(我花了大约一年的时间:D))。

最后,我会选择

do 
  #code
end

块。

答案 10 :(得分:1)

我提出另一个答案,虽然已经指出 big 差异(优先/绑定),这可能导致很难找到问题(Tin Man,其他人指出了这一点)。 我认为我的例子显示了一个不那么常见的代码片段的问题,即使是经验丰富的程序也不像周日时间那样阅读:

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不存在...

为什么会发生这种情况在这里指出不止一个 - 优先。但是在哪里放括号呢? (@ Tin Man:我总是像你一样使用牙套,但这里......监督)

所以每个答案都像

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

答案 11 :(得分:1)

我的个人风格是强调对{ ... } vs 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
}

虽然缺乏严格的规则可能会为不同的程序员产生不同的选择,但我认为尽管是主观的,但可读性的逐案优化是坚持严格规则的净收益。

答案 12 :(得分:0)

我在这里举了一个投票率最高的答案的例子。说,

[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

即它接受一段代码- do end 之间的任何内容都作为yield执行。然后结果将再次作为数组收集,从而返回一个全新的对象。

因此,每当遇到块或 {} 时,只需 思维导图,代码块作为参数传递, 将在内部执行。

答案 13 :(得分:-2)

还有第三种选择:编写预处理器以推断&#34;结束&#34;从它自己的路线,缩进。喜欢简洁代码的深层思考者恰好是正确的。

更好的是,破解红宝石所以这是一面旗帜。

当然,&#34;选择你的战斗&#34;最简单的解决方案是采用样式约定,即一系列结尾都出现在同一行上,并教你的语法着色以使它们静音。为了便于编辑,可以使用编辑器脚本来扩展/折叠这些序列。

我的红宝石代码行的20%到25%是&#34;结束&#34;在他们自己的路线上,我的缩进惯例都可以简单地推断出来。 Ruby是类似lisp的语言,取得了最大的成功。当人们对此提出异议时,询问所有可怕的括号在哪里,我向他们展示了一个红宝石功能,以七行多余的结尾&#34;结束&#34;。

我使用lisp预处理程序编写了多年的代码来推断大多数括号:一个条&#39; |&#39;开了一个在线路末端自动关闭的组,还有一个美元符号&#39; $&#39;作为一个空的占位符,否则将没有符号来帮助推断分组。这当然是宗教战争领土。没有括号的Lisp / scheme是所有语言中最具诗意的。尽管如此,使用语法着色简单地使括号静音更容易。

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