do..end vs括号中的大括号


154

我有一个同事正在积极尝试说服我不要使用do..end,而应使用花括号在Ruby中定义多行块。

我坚决只使用短括号来代替大括号,而对其他所有东西都做完。但是我认为我会与更广泛的社区联系以获得一些解决方案。

那是什么,为什么?(一些Shoulda代码示例)

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

就个人而言,仅看上述内容就可以为我解答问题,但我想向更大的社区开放。


3
只是想知道,但是您的同事对使用括号的争论是什么?似乎更像是个人喜好而不是逻辑上的事情。
Daniel Lee

6
如果您要讨论,请使其成为社区Wiki。给您的问题:这只是个人风格。我喜欢花括号,因为它们看起来比较书呆。
Halfdan

7
这不是什么偏爱,除了语法上的原因外,还有一个语法上的原因要使用一个。有几个答案说明了原因。未能使用正确的选择可能会导致非常细微的错误,如果做出另一种“样式”选择却从未对方法使用包装括号,则很难找到它们。
Tin Man

1
“边缘条件”有一个不习惯潜伏在不了解它们的人身上的习惯。防御性编码意味着很多事情,包括决定使用编码样式,以最大程度地减少遇到案件的机会。您可能会知道,但是您之后的两个人可能不会忘记部落的知识。不幸的是,它倾向于在公司环境中发生。
Tin Man

1
@tinman这些类型的边缘条件由TDD处理。这就是为什么Rubist可以编写这样的代码并使整夜安息的原因,因为他们知道代码中不存在此类错误。当使用TDD或BDD进行开发时,会出现这样的优先级错误,屏幕上显示的红色使我们想起“部落知识”。通常,解决方案是在标准约定中完全可以接受的地方添加几个括号。:)
Blake Taylor

Answers:


246

一般约定是将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,因此在确定要使用的内容时请记住这一点。

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


44
我赞成始终在方法参数周围使用括号来回避整个问题。
Tin Man

@锡人:您甚至在Rake中使用括号吗?
Andrew Grimm

9
对我来说,这是一种老式的编程习惯。如果语言支持括号,则使用它们。
Tin Man

8
+1用于指出优先级问题。当我尝试转换为do时,有时会遇到意外的异常。结束于{}
idrinkpabst

1
为什么puts [1,2,3].map do |k| k+1; end只打印枚举器,即一件事。是不是放过去了两个参数就是在这里[1,2,3].mapdo |k| k+1; end
2014年

55

Ruby编程

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

所以代码

f param {do_something()}

param在代码中将块绑定到变量

f param do do_something() end

将块绑定到函数f

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


7
+1“但是,如果将函数参数括在括号中,则这不是问题。”。我这样做只是出于习惯,而不是依靠机会和口译员的异想天开。:-)
锡人

4
@theTinMan,如果您的解释器在其解析器中使用机会,则需要一个新的解释器。
Paul Draper

@PaulDraper-的确如此,但并非所有语言都同意这个问题。但是(我认为)parens很少“不做自己的工作”是非常罕见的,因此普遍使用parens可以使您不必记住语言设计者的“随机”决定-即使它们是由编译器和解释器的实现者忠实执行的。
dlu

15

对此有一些观点,这实际上是个人喜好问题。许多红宝石学家都采用您的方法。但是,其他两种常见的样式是始终使用一种或另一种,或者{}用于返回值do ... end的块以及用于执行副作用的块。


2
这不仅仅是个人喜好。如果方法参数未括在括号中,则参数绑定会导致细微的错误。
Tin Man

1
我目前没有做最后一个选择,但是我提到某些人采用这种方法是+1。
Andrew Grimm

Avdi Grimm在博客文章中倡导这一点:devblog.avdi.org/2011/07/26/…–
alxndr

8

花括号的一个主要好处是-许多编辑器的匹配时间更容易,使某些类型的调试更加容易。同时,关键字“ do ... end”很难匹配,尤其是因为“ end”也匹配“ if”。


以RubyMine为例,do ... end默认情况下突出显示关键字
ck3g 2011年

1
虽然我确实喜欢大括号,但我认为没有理由让编辑者很难找到2/3字符串而不是单个字符。尽管有一种观点认为大多数编辑器已经匹配了花括号,但是do ... end这是一种特殊情况,需要特定于语言的逻辑。
Chinoto Vokro '16


7

我正在投票赞成/结束


该约定适用do .. end于多线和{ ... }单线。

但是我do .. end更喜欢,所以当我只有一根衬纸时,do .. end无论如何我都会使用它,但是像往常一样格式化它,以便做三行。这使每个人都开心。

  10.times do 
    puts ...
  end

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

另外,我已经{ }在C程序中看到足够多的内容。像往常一样,Ruby的方法更好。恰好只有一种类型的if块,而您不必回头将语句转换为复合语句。


2
“我已经在C程序中看到足够的{}了” ...和Perl。并且,+ 1表示其中的多个语句,并倡导Ruby的zen-like编码风格。:-)
锡人

1
严格来说,还有另一种“ if”语法-后缀“ if”(do_stuff if x==1),转换为常规语法有点烦人。但这是另一个讨论。
开尔文

有前缀if,后缀if,后缀unless(也是一种if,如果不是)和if带有的一行then。Ruby的方式是有很多方式来表达实际上总是相同的东西,比C遵循非常简单,严格的规则所提供的东西要糟糕得多。
Mecki

2
因此,如果我们喜欢C语言中的{},那意味着我们也应该在Ruby中使用它们吗?
沃伦·露

6

一些有影响力的红宝石主义者建议在使用返回值时使用花括号,而在不使用返回值时使用/结束。

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

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


2

实际上,这是个人喜好,但是,在过去三年的红宝石经验中,我了解到红宝石具有其风格。

一个示例是,如果您是从JAVA后台来的,则可以使用布尔方法

def isExpired
  #some code
end 

请注意骆驼的情况,最常见的是“ is”前缀,以将其标识为布尔方法。

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

def expired?
  #code
end

因此,我个人认为,最好使用“红宝石方式”(但我知道一个人要花一些时间才能理解(我花了大约1年时间:D))。

最后,我会去

do 
  #code
end

块。


2

我已经提出了另一个答案,尽管已经指出了很大的区别(优先/约束力),但是这可能会导致很难发现问题(锡曼等人指出了这一点)。我认为我的示例显示了一个不那么常见的代码片段的问题,即使是经验丰富的程序员也不像星期天那样阅读:

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

(extend的结果与该块有什么关系……)

因此,如果要使用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

1

归结为个人偏见,我更喜欢使用花括号而不是do / end块,因为多数背景语言都在do / end约定下使用花括号,因此对于更多开发人员而言,花括号更易于理解。话虽这么说,真正的关键是要在您的商店内达成协议,如果6/10开发人员使用do / end,那么每个人都应该使用do / end,如果6/10使用花括号,则坚持该范例。

一切都与制作模式有关,以便整个团队可以更快地识别代码结构。


3
这不仅仅是偏爱。两者之间的绑定是不同的,并且可能导致难以诊断的问题。有几个答案详细说明了问题。
Tin Man

4
对于其他语言背景的人来说,我认为在这种情况下,“可理解性”的差异很小,因此不值得考虑。(顺便说一句,这不是我的不满。)
Kelvin


0

我个人的风格是强调可读性超过硬性规定{... }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
}

尽管缺乏严格的规则可能会为不同的程序员产生明显不同的选择,但我认为针对案例的可读性逐案优化虽然是主观的,但相对于遵守严格的规则而言却是净收益。


1
来自Python,也许我应该跳过Ruby而学习Wisp。
Cees Timmerman'2

我曾经相信那Ruby是最好的实用语言。我应该说脚本语言。今天,我认为您的学习状况同样会很好Brainfuck
Boris Stitnicky

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屈服被执行。然后结果将再次作为数组收集,因此它返回一个全新的对象。

因此,每当遇到do-end块或{}时,只要记住一个思维导图,即代码块将作为参数传递,它将在内部执行。


-3

第三种选择是:编写一个预处理程序以根据缩进在其自己的行上推断“ end”。喜欢简洁代码的深刻思想家恰好是对的。

更好的是,入侵红宝石,这是一个标志。

当然,“打架”最简单的解决方案是采用一种样式惯例,即所有结尾都出现在同一行上,并教您使用语法着色使它们静音。为了便于编辑,可以使用编辑器脚本来扩展/折叠这些序列。

我的ruby代码行的20%到25%在它们自己的行上是“结尾”,所有缩进都由我的缩进约定微不足道地推断出来。Ruby是获得极大成功的类似Lisp的语言。当人们对此提出质疑时,询问所有可怕的括号在哪里,我向他们展示了一个以七行多余的“结束”结尾的红宝石函数。

我使用lisp预处理程序来推断大多数括号的代码已经很多年了:竖线“ |” 打开一个在行尾自动关闭的组,美元符号“ $”用作空的占位符,否则将没有任何符号可以帮助推断分组。这当然是宗教战争的领域。没有括号的Lisp / scheme是所有语言中最富有诗意的。尽管如此,使用语法着色简单地使括号安静即可。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.