do..end vs curly braces for blocks in Ruby

2019-01-02 17:03发布

I have a coworker who is actively trying to convince me that I should not use do..end and instead use curly braces for defining multiline blocks in Ruby.

I'm firmly in the camp of only using curly braces for short one-liners and do..end for everything else. But I thought I would reach out to the greater community to get some resolution.

So which is it, and why? (Example of some shoulda code)

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

or

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

Personally, just looking at the above answers the question for me, but I wanted to open this up to the greater community.

13条回答
像晚风撩人
2楼-- · 2019-01-02 17:26

There is a subtle difference between them, but { } binds tighter than do/end.

查看更多
冷夜・残月
3楼-- · 2019-01-02 17:27

The general convention is to use do..end for multi-line blocks and curly braces for single line blocks, but there is also a difference between the two that can be illustrated with this example:

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

This means that {} has a higher precedence than do..end, so keep that in mind when deciding what you want to use.

P.S: One more example to keep in mind while you develop your preferences.

The following code:

task :rake => pre_rake_task do
  something
end

really means:

task(:rake => pre_rake_task){ something }

And this code:

task :rake => pre_rake_task {
  something
}

really means:

task :rake => (pre_rake_task { something })

So to get the actual definition that you want, with curly braces, you must do:

task(:rake => pre_rake_task) {
  something
}

Maybe using braces for parameters is something you want to do anyways, but if you don't it's probably best to use do..end in these cases to avoid this confusion.

查看更多
高级女魔头
4楼-- · 2019-01-02 17:32

There's a third option: Write a preprocessor to infer "end" on its own line, from indentation. The deep thinkers who prefer concise code happen to be right.

Better yet, hack ruby so this is a flag.

Of course, the "pick your fights" simplest solution is to adopt the style convention that a sequence of ends all appear on the same line, and teach your syntax coloring to mute them. For ease of editing, one could use editor scripts to expand/collapse these sequences.

20% to 25% of my ruby code lines are "end" on their own line, all trivially inferred by my indentation conventions. Ruby is the lisp-like language to have achieved the greatest success. When people dispute this, asking where are all the ghastly parentheses, I show them a ruby function ending in seven lines of superfluous "end".

I did code for years using a lisp preprocessor to infer most parentheses: A bar '|' opened a group that autoclosed at the end of the line, and a dollar sign '$' served as an empty placeholder where there would otherwise be no symbols to help infer the groupings. This is of course religious war territory. Lisp/scheme without the parentheses is the most poetic of all languages. Still, it is easier to simply quiet down parentheses using syntax coloring.

I still code with a preprocessor for Haskell, to add heredocs, and to default all flush lines as comments, everything indented as code. I dislike comment characters, whatever the language.

查看更多
临风纵饮
5楼-- · 2019-01-02 17:37

I put another Answer, although the big difference was already pointed out (prcedence/binding), and that can cause hard to find problems (the Tin Man, and others pointed that out). I think my example shows the problem with a not so usual code snippet, even experienced programmeres do not read like the sunday times:

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

Then i did some code beautifying ...

#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

if you change the {} here to do/end you will get the error, that method translate does not exist ...

Why this happens is pointed out here more than one - precedence. But where to put braces here? (@the Tin Man: I always use braces, like you, but here ... overseen)

so every answer like

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

is just wrong if used without the "BUT Keep an eye on braces / precedence!"

again:

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

and

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

(what ever the result of extend does with the block ...)

So if you want to use do/end use this:

#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
查看更多
冷夜・残月
6楼-- · 2019-01-02 17:40

There is one major benefit to curly braces - many editors have a MUCH easier time of matching them, making certain types of debugging much easier. Meanwhile, the keyword "do...end" is quite a bit harder to match, especially since "end"s also match "if"s.

查看更多
姐姐魅力值爆表
7楼-- · 2019-01-02 17:41

Actually it's a personal preference, but having said that, for past 3 years of my ruby experiences what I have learnt is that ruby has its style.

One example would be, if you are comming from a JAVA background , for a boolean method you might use

def isExpired
  #some code
end 

notice the camel case and most often 'is' prefix to identify it as a boolean method.

But in ruby world, the same method would be

def expired?
  #code
end

so I personally think, it's better to go with 'ruby way' (But I know it takes some time for one to understand (it took me around 1 year :D)).

Finally, I would go with

do 
  #code
end

blocks.

查看更多
登录 后发表回答