【问题标题】:do..end vs curly braces for blocks in Rubydo..end vs Ruby中块的花括号
【发布时间】:2011-04-07 20:32:52
【问题描述】:

我有一位同事积极地试图说服我不应该使用 do..end 而是使用花括号在 Ruby 中定义多行块。

我坚定地坚持只对短单行使用大括号,而对其他所有内容都使用..end。但我想我会与更大的社区联系以获得一些解决方案。

那么它是什么,为什么? (一些应该代码的例子)

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

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

【问题讨论】:

  • 只是想知道您的同事对使用大括号的看法是什么?看起来更像是个人喜好而不是逻辑。
  • 如果您想要讨论,请将其设为社区 wiki。对于你的问题:这只是个人风格。我更喜欢花括号,因为它们看起来更书呆子。
  • 这不是一种偏好,除了风格上的原因之外,还有语法上的原因。几个答案解释了原因。未能使用正确的错误可能会导致非常微妙的错误,如果做出另一种“风格”选择,即永远不要对方法使用换行括号,则很难找到这些错误。
  • “边缘条件”有偷偷摸摸不了解他们的人的坏习惯。防御性编码意味着很多事情,包括决定使用能够最大限度地减少遇到案例的机会的编码风格。你可能知道,但你之后的那个家伙可能不会在部落知识被遗忘之后。不幸的是,这种情况往往发生在企业环境中。
  • @tinman 这些类型的边缘条件由 TDD 处理。这就是为什么 Rubist 可以编写这样的代码并在知道他们的代码中不存在此类错误的情况下整夜休息。在使用 TDD 或 BDD 进行开发时,出现这种优先级错误,屏幕上显示的红色提醒我们“部落知识”。通常解决方案是在标准约定中完全可以接受的地方添加几个括号。 :)

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


【解决方案1】:

一般约定是多行块使用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,因此在决定要使用什么时请记住这一点。

在您发展自己的偏好时,还要记住一个示例。

以下代码:

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

【讨论】:

  • 为什么puts [1,2,3].map do |k| k+1; end 只打印枚举数,即一件事。 puts 不是在这里传递了两个参数,即[1,2,3].mapdo |k| k+1; end
  • github.com/bbatsov/rubocop/issues/1520 f = lambda do |param| ... end scope :some_scope, f 也许这看起来很单调,然后还有另一种选择,我认为它很难看: scope :some_scope, (lambda do |param| ... end)
  • [1,2,3].map do |k| puts k+1 end 似乎比puts [1,2,3].map { |k| k+1 }更具可读性
  • @ben: 没错,因为它是puts 获得了两个参数。首先.map 被评估,但它没有得到一个块(因为看起来像块的东西不是.map 的参数),所以它返回一个成为puts 第一个参数的枚举器。而puts 将获得该块作为第二个参数,但puts 不使用任何传递的块,因此没有任何反应。括号解决了这个问题:puts ([1,2,3].map do |k| k+1; end)
【解决方案2】:

来自Programming Ruby

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

所以代码

f param {do_something()}

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

f param do do_something() end

将块绑定到函数f

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

【讨论】:

    【解决方案3】:

    对此有几点看法,这真的是个人喜好问题。许多红宝石学家采用您所做的方法。但是,另外两种常见的样式是始终使用其中一种,或者将 {} 用于返回值的块,将 do ... end 用于执行副作用的块。

    【讨论】:

    • 这不仅仅是个人喜好问题。如果方法参数未包含在括号中,则参数绑定可能会导致细微的错误。
    【解决方案4】:

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

    【讨论】:

    • 以RubyMine为例,默认高亮do ... end关键字
    • 虽然我更喜欢大括号,但我认为编辑器在查找 2/3 字符串而不是单个字符时会遇到困难。尽管有观点认为大多数编辑器已经匹配大括号,而 do ... end 是一种特殊情况,需要语言特定的逻辑。
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      我投票支持 do / end


      约定是do .. end 用于多行,{ ... } 用于单行。

      但我更喜欢do .. end,所以当我有一个衬里时,无论如何我都会使用do .. end,但像往常一样将其格式化为三行中的do/end。这让每个人都开心。

        10.times do 
          puts ...
        end
      

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

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

      【讨论】:

      • 严格来说,还有另一种“if”语法——后缀“if”(do_stuff if x==1),转换成常规语法有点烦人。但这是另一个讨论。
      • 有前缀if,后缀if,后缀unless(也是if的一种,if-not)和一行ifthen。 Ruby 的方式是有很多方式来表达实际上总是相同的东西,比 C 提供的遵循非常简单、严格的规则的东西要糟糕得多。
      【解决方案7】:

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

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

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

      【讨论】:

        【解决方案8】:

        其实这是个人喜好,不过话说回来,在我过去 3 年的 ruby​​ 体验中,我学到的是 ruby​​ 有它的风格。

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

        def isExpired
          #some code
        end 
        

        注意驼峰式大小写和最常见的“is”前缀,以将其标识为布尔方法。

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

        def expired?
          #code
        end
        

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

        最后,我会选择

        do 
          #code
        end
        

        块。

        【讨论】:

          【解决方案9】:

          我提出了另一个答案,尽管已经指出了 差异(优先/绑定),这可能会导致难以发现的问题(铁皮人和其他人指出了这一点)。 我认为我的示例显示了一个不太常见的代码 sn-p 的问题,即使是有经验的程序员也不会像周日时报那样阅读:

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

          这里指出的原因不止一个 - 优先级。但是在哪里放牙套呢? (@the 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
          

          (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
          

          【讨论】:

            【解决方案10】:

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

            这一切都是为了制作一个模式,以便整个团队能够更快地识别代码结构。

            【讨论】:

            • 这不仅仅是偏好。两者之间的绑定不同,可能会导致难以诊断的问题。几个答案详细说明了这个问题。
            • 就其他语言背景的人而言 - 我认为在这种情况下“可理解性”的差异是如此之小,以至于不值得考虑。 (顺便说一句,这不是我的反对意见。)
            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

              我的个人风格是强调可读性,而不是 {...}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
              }
              

              尽管缺乏严格的规则可能会为不同的程序员产生略微不同的选择,但我相信针对可读性的逐个优化虽然是主观的,但相对于遵守严格的规则而言是一种净收益。

              【讨论】:

                【解决方案13】:

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

                [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
                

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

                所以当你遇到 do-end 块或 { } 时,只要有一个 将代码块作为参数传递的思维导图, 将在内部执行。

                【讨论】:

                  【解决方案14】:

                  还有第三种选择:编写一个预处理器,根据缩进在自己的行上推断“结束”。喜欢简洁代码的深刻思考者恰好是对的。

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

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

                  我的 ruby​​ 代码行中有 20% 到 25% 是在他们自己的行上“结束”的,所有这些都是由我的缩进约定简单地推断出来的。 Ruby 是取得了最大成功的类 lisp 语言。当人们对此提出异议时,询问所有可怕的括号在哪里,我向他们展示了一个以七行多余的“结束”结尾的 ruby​​ 函数。

                  多年来我一直使用 lisp 预处理器编写代码来推断大多数括号:条形“|”打开一个在行尾自动关闭的组,美元符号“$”用作空占位符,否则将没有符号来帮助推断分组。这当然是宗教战争的领土。没有括号的 Lisp/scheme 是所有语言中最富有诗意的。尽管如此,使用语法着色来简单地消除括号更容易。

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

                  【讨论】:

                    猜你喜欢
                    • 2011-07-27
                    • 1970-01-01
                    • 2020-10-21
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-10-06
                    • 2012-01-18
                    相关资源
                    最近更新 更多