У меня есть коллега, который активно пытается убедить меня, что я не должен использовать 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...
}
}
Лично, просто смотря на выше, отвечает на вопрос для меня, но я хотел открыть это для большего сообщества.
Общим соглашением является использование 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 в этих случаях, чтобы избежать этой путаницы.
Скобки имеют высокий приоритет; do имеет низкий приоритет. Если вызов метода имеет параметры, которые не заключены в круглые скобки, форма скобки блока будет привязываться к последнему параметру, а не к общему вызову. Форма do будет привязываться к вызову.
Итак, код
f param {do_something()}
Привязывает блок к переменной param
, а код
f param do do_something() end
Привязывает блок к функции f
.
Однако это не проблема, если вы заключите аргументы функции в скобки.
Есть несколько точек зрения на это, это действительно вопрос личных предпочтений. Многие рубисты используют подход, который вы делаете. Тем не менее, два других стиля, которые являются общими, должны всегда использовать один или другой, или использовать {}
для блоков, возвращающих значения, и do ... end
для блоков, которые выполняются для побочных эффектов.
Есть одно важное преимущество для фигурных скобок - многие редакторы имеют намного более легкое время для их сопоставления, что делает некоторые типы отладки намного проще. Между тем ключевое слово "do... end" довольно сложно сопоставить, тем более, что "end" также соответствуют "if" s.
do ... end
по умолчанию
do ... end
- это особый случай, который требует специфической для языка логики.
Согласование do .. end
для многострочных и { ... }
для однострочных.
Но мне нравится do .. end
лучше, поэтому, когда у меня есть один вкладыш, я все равно использую do .. end
, но форматирую его как обычно для do/end в трех строках. Это делает всех счастливыми.
10.times do
puts ...
end
Одна проблема с { }
заключается в том, что она является стилем-режим-враждебным (потому что они жестко привязаны к последнему параметру, а не ко всему вызову метода, поэтому вы должны включить метод parens), и они просто, на мой взгляд, дон Я выгляжу так же хорошо. Они не являются группами операторов, и они сталкиваются с хэш-константами для удобочитаемости.
Кроме того, я видел достаточно { }
в программах на C. Рубиновый путь, как обычно, лучше. Существует только один тип блока if
, и вам никогда не придется возвращаться и преобразовывать инструкцию в составной оператор.
Наиболее распространенное правило, которое я видел (совсем недавно в Eloquent Ruby):
Пара влиятельных рубистов предлагает использовать фигурные скобки, когда вы используете возвращаемое значение, и 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 вместо всего вызова метода. Просто добавьте круглые скобки, чтобы этого не было.
Мой личный стиль - подчеркнуть читаемость по жестким правилам выбора {
... }
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
}
Несмотря на то, что отсутствие жестких правил может привести к сколь угодно различным вариантам для разных программистов, я считаю, что индивидуальная оптимизация для читаемости, хотя и субъективная, является чистым преимуществом по сравнению с жесткими правилами.
Ruby
- лучший прагматичный язык. Должен ли я сказать скриптовый язык. Сегодня я думаю, что вы были бы в равной степени хорошо изучать Brainfuck
.
Я поставил другой ответ, хотя большая разница уже была указана (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
На самом деле это личное предпочтение, но, сказав, что за последние 3 года моих рубиновых переживаний я узнал, что у рубина свой стиль.
Одним из примеров может быть, если вы отправляетесь с фона JAVA, для логического метода, который вы можете использовать
def isExpired
#some code
end
Обратите внимание на случай верблюда и чаще всего на "префикс", чтобы идентифицировать его как логический метод.
Но в мире рубинов тот же метод будет
def expired?
#code
end
поэтому я лично думаю, что лучше идти с "рубиновым способом" (Но я знаю, что для понимания нужно какое-то время (мне понадобилось около 1 года: D)).
Наконец, я бы пошел с
do
#code
end
блоки.
Между ними существует тонкая разница, но {} связывает более жесткие, чем do/end.
Попадает в личную предвзятость, я предпочитаю фигурные скобки над блоком do/end, поскольку он более понятен более высокому числу разработчиков из-за того, что большинство исходных языков используют их по соглашению do/end. С учетом этого реальный ключ заключается в том, чтобы прийти к соглашению в вашем магазине, если do/end используется разработчиками 6/10, чем EVERYONE должен их использовать, если 6/10 используют фигурные скобки, а затем придерживайтесь этой парадигмы.
Все о создании шаблона, чтобы команда в целом могла быстрее идентифицировать структуры кода.
Есть третий вариант: напишите препроцессор, чтобы вывести "конец" в свою строку, от отступа. Глубокие мыслители, которые предпочитают сжатый код, оказались правы.
Еще лучше, взломайте рубин, так что это флаг.
Конечно, самое лучшее решение "выбрать ваши бои" - это принять соглашение о стиле, что последовательность концов все отображается в одной строке, и научите окраску синтаксиса отключать их. Для простоты редактирования можно использовать скрипты редактора для расширения/сглаживания этих последовательностей.
От 20% до 25% моих строк рубинового кода "заканчиваются" в своей строке, все тривиально выведенные моими соглашениями об отступлении. Ruby - это lisp -подобный язык, который достиг наибольшего успеха. Когда люди оспаривают это, спрашивая, где находятся все ужасные круглые скобки, я показываю им рубиновую функцию, оканчивающуюся на семь строк лишнего "конца".
Я делал код в течение многих лет, используя препроцессор lisp для вывода большинства круглых скобок: бар '|' открыла группу, которая была автоматически закрыта в конце строки, а знак доллара "$" служил пустым заполнителем, где в противном случае не было бы символов, которые могли бы помочь вывести группы. Это, конечно, территория религиозной войны. Lisp/схема без круглых скобок является наиболее поэтической из всех языков. Тем не менее, проще просто замолчать круглые скобки, используя синтаксическую раскраску.
Я все еще код с препроцессором для Haskell, чтобы добавить heredocs, и по умолчанию все строки флеша в качестве комментариев, все отступы как код. Мне не нравятся символы комментариев, независимо от языка.