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

155

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

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

Так что это и почему? (Пример некоторого кода musta)

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
Просто интересно, а каковы аргументы ваших коллег по использованию брекетов? Это больше похоже на личные предпочтения, чем на логику.
Даниэль Ли,
6
Если вы хотите обсуждение, сделайте его вики-сообществом. На ваш вопрос: это просто личный стиль. Я предпочитаю фигурные скобки, поскольку они выглядят более всезнайки.
Халфдан
7
Это не предпочтение, есть синтаксические причины для использования одного над другим в дополнение к стилистическим причинам. Несколько ответов объясняют почему. Неправильное использование правильного может привести к очень тонким ошибкам, которые трудно найти, если сделан другой «стилистический» выбор - никогда не использовать переносящие скобки для методов.
Жестянщик
1
«Краевые условия» имеют плохую привычку подкрадываться к людям, которые о них не знают. Защитное кодирование означает много вещей, в том числе решение использовать стили кодирования, которые сводят к минимуму вероятность столкновения с кейсами. Вы, возможно, знаете, но парень, которого двое после вас, может быть, не будет после того, как знания о племени были забыты. К сожалению, это происходит в корпоративной среде.
Жестянщик
1
@tinman Эти типы краевых условий обрабатываются TDD. Вот почему Rubist может написать такой код и получить полный ночной отдых, зная, что таких ошибок нет в их коде. При разработке с TDD или BDD и допускается такая ошибка в приоритете, красный цвет, показанный на экране, напоминает нам о «племенном знании». Обычно решение состоит в том, чтобы добавить пару паренов где-то, что вполне приемлемо в рамках стандартных соглашений. :)
Блейк Тейлор

Ответы:

247

Общее соглашение состоит в том, чтобы использовать 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
Я голосую за то, чтобы всегда использовать круглые скобки вокруг параметров метода, чтобы обойти весь вопрос.
Жестянщик
@ The Tin Man: Вы даже используете скобки в Rake?
Эндрю Гримм
9
Для меня это привычка программирования старой школы. Если язык поддерживает круглые скобки, я их использую.
Жестянщик
8
+1 за указание на проблему приоритета. Иногда я получаю неожиданные исключения, когда пытаюсь и преобразовываю do; конец {}
idrinkpabst
1
Почему puts [1,2,3].map do |k| k+1; endперечислитель перечисляет только одно. Не путы прошли два аргумента здесь именно [1,2,3].mapи do |k| k+1; end?
Бен
55

Из программирования Ruby :

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

Итак, код

f param {do_something()}

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

f param do do_something() end

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

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

Дэвид Браун
источник
7
+1 «Однако это не проблема, если вы заключаете аргументы функций в круглые скобки.». Я делаю это по привычке, а не полагаюсь на случайность и прихоть переводчика. :-)
Жестянщик
4
@theTinMan, если ваш интерпретатор использует шанс в своем парсере, вам нужен новый интерпретатор.
Пол Дрейпер
@PaulDraper - действительно, но не все языки согласны с этим вопросом. Но (я думаю) для редких парней редко «не делают свою работу», поэтому использование паренсов повсеместно избавляет вас от необходимости запоминать «случайные» решения, принимаемые языковыми дизайнерами - даже если они точно выполняются разработчиками компиляторов и интерпретаторов ,
DLU
15

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

GSto
источник
2
Это не только вопрос личных предпочтений. Связывание параметров может привести к незначительным ошибкам, если параметры метода не заключены в круглые скобки.
Жестянщик
1
В настоящее время я не делаю последний вариант, но я + 1-ин за упоминание, что некоторые люди используют этот подход.
Эндрю Гримм
Авди Гримм выступает за это в своем блоге: devblog.avdi.org/2011/07/26/…
alxndr
8

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

GlyphGryph
источник
Например, RubyMine выделяет do ... endключевые слова по умолчанию
ck3g
1
Хотя я предпочитаю фигурные скобки, я не вижу причины, по которой у редактора возникнут проблемы с поиском строки символов 2/3 по сравнению с одним символом. Хотя есть аргумент, что большинство редакторов уже соответствуют скобкам, тогда do ... endкак это особый случай, который требует специфической для языка логики.
Чиното Вокро
7

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

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

Я голосую за до / конец


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

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

  10.times do 
    puts ...
  end

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

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

DigitalRoss
источник
2
«Я видел достаточно {} в программах на C» ... и Perl. И +1 за несколько заявлений и за поддержку стилей кодирования в Ruby. :-)
Жестянщик
1
Строго говоря, есть еще один синтаксис «если» - постфикс «if» ( do_stuff if x==1), который немного раздражает при преобразовании в обычный синтаксис. Но это другое обсуждение.
Кельвин
Есть префикс if, постфикс if, постфикс unless(также вид if, если-нет) и одна строка ifс then. Способ Ruby состоит в том, чтобы иметь множество способов выразить то, что на самом деле всегда одно и то же, гораздо хуже, чем то, что предлагает C, и следует очень простым и строгим правилам.
Меки
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

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

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

def isExpired
  #some code
end 

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

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

def expired?
  #code
end

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

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

do 
  #code
end

блоки.

sameera207
источник
2

Я поставил другой ответ, хотя большая разница уже была указана (prcedence / binding), и это может привести к трудностям при поиске проблем (Жестянщик и другие указали на это). Я думаю, что мой пример показывает проблему с не очень обычным фрагментом кода, даже опытные программисты не читают как воскресные времена:

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

(что когда-либо делает результат с блоком ...)

Так что если вы хотите использовать 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
halfbit
источник
1

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

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

Джейк Калстад
источник
3
Это больше, чем предпочтение. Связь между ними различна и может вызвать проблемы, которые трудно диагностировать. Несколько ответов подробно описывают проблему.
Жестянщик
4
Что касается людей с другим языковым прошлым - я думаю, что разница в «понятности» в этом случае настолько мала, что не требует рассмотрения. (Между прочим, это не было моим отрицательным голосом.)
Кельвин
1

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

Шанкар Раджу
источник
0

Мой личный стиль состоит в том, чтобы подчеркнуть удобочитаемость, а не жесткие правила {... }против 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.
Сис Тиммерман
Раньше я считал, Rubyчто это лучший прагматичный язык. Должен ли я сказать скриптовый язык. Сегодня я думаю, что вы были бы в равной степени хорошо учиться Brainfuck.
Борис Ститницкий
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 и end , выполняется как yield. Затем результат будет снова собран как массив, таким образом, он возвращает совершенно новый объект.

Поэтому всякий раз, когда вы сталкиваетесь с блоком do-end или {} , просто имейте в виду, что блок кода передается как параметр, который будет выполняться внутри.

Васант Саминатан
источник
-3

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

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

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

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

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

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

Сизигии
источник