Do..end vs фигурные скобки для блоков в Ruby

У меня есть коллега, который активно пытается убедить меня, что я не должен использовать do..end и вместо этого использовать фигурные скобки для определения многострочных блоков в Ruby.

Я твердо в лагере, используя только фигурные скобки для коротких однострочных и делаю.. для всего остального. Но я думал, что я обращусь к большему сообществу, чтобы получить определенную резолюцию.

Итак, что это такое и почему? (Пример некоторого кода toa)

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

Лично, просто смотря на выше, отвечает на вопрос для меня, но я хотел открыть это для большего сообщества.

Ответ 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, поэтому имейте это в виду при выборе того, что вы хотите использовать.

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 в этих случаях, чтобы избежать этой путаницы.

Ответ 2

От Программирование Ruby:

Скобки имеют высокий приоритет; do имеет низкий приоритет. Если вызов метода имеет параметры, которые не заключены в круглые скобки, форма скобки блока будет привязываться к последнему параметру, а не к общему вызову. Форма do будет привязываться к вызову.

Итак, код

f param {do_something()}

Привязывает блок к переменной param, а код

f param do do_something() end

Привязывает блок к функции f.

Однако это не проблема, если вы заключите аргументы функции в скобки.

Ответ 3

Есть несколько точек зрения на это, это действительно вопрос личных предпочтений. Многие рубисты используют подход, который вы делаете. Тем не менее, два других стиля, которые являются общими, должны всегда использовать один или другой, или использовать {} для блоков, возвращающих значения, и do ... end для блоков, которые выполняются для побочных эффектов.

Ответ 4

Есть одно важное преимущество для фигурных скобок - многие редакторы имеют намного более легкое время для их сопоставления, что делает некоторые типы отладки намного проще. Между тем ключевое слово "do... end" довольно сложно сопоставить, тем более, что "end" также соответствуют "if" s.

Ответ 5

Я голосую за do/end


Согласование do .. end для многострочных и { ... } для однострочных.

Но мне нравится do .. end лучше, поэтому, когда у меня есть один вкладыш, я все равно использую do .. end, но форматирую его как обычно для do/end в трех строках. Это делает всех счастливыми.

  10.times do 
    puts ...
  end

Одна проблема с { } заключается в том, что она является стилем-режим-враждебным (потому что они жестко привязаны к последнему параметру, а не ко всему вызову метода, поэтому вы должны включить метод parens), и они просто, на мой взгляд, дон Я выгляжу так же хорошо. Они не являются группами операторов, и они сталкиваются с хэш-константами для удобочитаемости.

Кроме того, я видел достаточно { } в программах на C. Рубиновый путь, как обычно, лучше. Существует только один тип блока if, и вам никогда не придется возвращаться и преобразовывать инструкцию в составной оператор.

Ответ 6

Наиболее распространенное правило, которое я видел (совсем недавно в Eloquent Ruby):

  • Если это многострочный блок, используйте do/end
  • Если это один строковый блок, используйте {}

Ответ 7

Пара влиятельных рубистов предлагает использовать фигурные скобки, когда вы используете возвращаемое значение, и do/end, когда вы этого не сделаете.

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 в одной строке, потому что его труднее читать.

Вам нужно быть более осторожным, используя фигурные скобки, потому что он свяжется с методом final param вместо всего вызова метода. Просто добавьте круглые скобки, чтобы этого не было.

Ответ 8

Попадает в личную предвзятость, я предпочитаю фигурные скобки над блоком do/end, поскольку он более понятен более высокому числу разработчиков из-за того, что большинство исходных языков используют их по соглашению do/end. С учетом этого реальный ключ заключается в том, чтобы прийти к соглашению в вашем магазине, если do/end используется разработчиками 6/10, чем EVERYONE должен их использовать, если 6/10 используют фигурные скобки, а затем придерживайтесь этой парадигмы.

Все о создании шаблона, чтобы команда в целом могла быстрее идентифицировать структуры кода.

Ответ 9

Между ними существует тонкая разница, но {} связывает более жесткие, чем do/end.

Ответ 10

На самом деле это личное предпочтение, но, сказав, что за последние 3 года моих рубиновых переживаний я узнал, что у рубина свой стиль.

Одним из примеров может быть, если вы отправляетесь с фона JAVA, для логического метода, который вы можете использовать

def isExpired
  #some code
end 

Обратите внимание на случай верблюда и чаще всего на "префикс", чтобы идентифицировать его как логический метод.

Но в мире рубинов тот же метод будет

def expired?
  #code
end

поэтому я лично думаю, что лучше идти с "рубиновым способом" (Но я знаю, что для понимания нужно какое-то время (мне понадобилось около 1 года: D)).

Наконец, я бы пошел с

do 
  #code
end

блоки.

Ответ 11

Я поставил другой ответ, хотя большая разница уже была указана (prcedence/binding), и это может вызвать трудности при поиске проблем (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 не существует...

Почему это происходит, здесь указано более одного приоритета. Но где положить фигурные скобки здесь? (@The Tin Man: Я всегда использую фигурные скобки, как и вы, но здесь... контролируется)

поэтому каждый ответ вроде

If it a multi-line block, use do/end
If it 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

Ответ 12

Мой личный стиль - подчеркнуть читаемость по жестким правилам выбора {... } 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
}

Несмотря на то, что отсутствие жестких правил может привести к сколь угодно различным вариантам для разных программистов, я считаю, что индивидуальная оптимизация для читаемости, хотя и субъективная, является чистым преимуществом по сравнению с жесткими правилами.

Ответ 13

Есть третий вариант: напишите препроцессор, чтобы вывести "конец" в свою строку, от отступа. Глубокие мыслители, которые предпочитают сжатый код, оказались правы.

Еще лучше, взломайте рубин, так что это флаг.

Конечно, самое лучшее решение "выбрать ваши бои" - это принять соглашение о стиле, что последовательность концов все отображается в одной строке, и научите окраску синтаксиса отключать их. Для простоты редактирования можно использовать скрипты редактора для расширения/сглаживания этих последовательностей.

От 20% до 25% моих строк рубинового кода "заканчиваются" в своей строке, все тривиально выведенные моими соглашениями об отступлении. Ruby - это lisp -подобный язык, который достиг наибольшего успеха. Когда люди оспаривают это, спрашивая, где находятся все ужасные круглые скобки, я показываю им рубиновую функцию, оканчивающуюся на семь строк лишнего "конца".

Я делал код в течение многих лет, используя препроцессор lisp для вывода большинства круглых скобок: бар '|' открыла группу, которая была автоматически закрыта в конце строки, а знак доллара "$" служил пустым заполнителем, где в противном случае не было бы символов, которые могли бы помочь вывести группы. Это, конечно, территория религиозной войны. Lisp/схема без круглых скобок является наиболее поэтической из всех языков. Тем не менее, проще просто замолчать круглые скобки, используя синтаксическую раскраску.

Я все еще код с препроцессором для Haskell, чтобы добавить heredocs, и по умолчанию все строки флеша в качестве комментариев, все отступы как код. Мне не нравятся символы комментариев, независимо от языка.