do..end vs curly braces for blocks in Ruby

2019-01-02 17:00发布

问题:

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.

回答1:

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.



回答2:

From Programming Ruby:

Braces have a high precedence; do has a low precedence. If the method invocation has parameters that are not enclosed in parentheses, the brace form of a block will bind to the last parameter, not to the overall invocation. The do form will bind to the invocation.

So the code

f param {do_something()}

Binds the block to the param variable while the code

f param do do_something() end

Binds the block to the function f.

However this is a non-issue if you enclose function arguments in parenthesis.



回答3:

There a few points of view on this, it's really a matter of personal preference. Many rubyists take the approach you do. However, two other styles that are common is to always use one or the other, or to use {} for blocks that return values, and do ... end for blocks that are executed for side effects.



回答4:

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.



回答5:

I'm voting for do / end


The convention is do .. end for multiline and { ... } for one-liners.

But I like do .. end better, so when I have a one liner, I use do .. end anyway but format it as usual for do/end in three lines. This makes everyone happy.

  10.times do 
    puts ...
  end

One problem with { } is that it is poetry-mode-hostile (because they bind tightly to the last parameter and not the entire method call, so you must include method parens) and they just, to my mind, don't look as nice. They are not statement-groups and they clash with hash constants for readability.

Plus, I've seen enough of { } in C programs. Ruby's way, as usual, is better. There is exactly one type of if block, and you never have to go back and convert a statement into a compound-statement.



回答6:

The most common rule I've seen (most recently in Eloquent Ruby) is:

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


回答7:

A couple influential rubyists suggest to use braces when you use the return value, and do/end when you don't.

http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (on archive.org)

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (on archive.org)

This seems like a good practice in general.

I'd modify this principle a bit to say that you should avoid using do/end on a single line because it's harder to read.

You do have to be more careful using braces because it'll bind to a method's final param instead of the whole method call. Just add parentheses to avoid that.



回答8:

Comes down to personal bias, I prefer curly braces over a do/end block as its more understandable to a higher number of developers due to a majority of background languages use them over the do/end convention. With that being said the real key is to come to an agreement within your shop, if do/end is used by 6/10 developers than EVERYONE should be using them, if 6/10 use curly braces, then stick to that paradigm.

Its all about making a pattern so that the team as a whole can identify the code structures quicker.



回答9:

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



回答10:

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.



回答11:

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


回答12:

My personal style is to emphasize readability over rigid rules of {...} vs do...end choice, when such choice is possible. My idea of readability is as follows:

[ 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

In more complex syntax, such as multiline nested blocks, I try to intersperse {...} and do...end delimiters for most natural result, eg.

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
}

Although the lack of rigid rules might produce sligtly different choices for different programmers, I believe that case-by-case optimization for readability, although subjective, is a net gain over adherence to rigid rules.



回答13:

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.



标签: