Существуют ли непреднамеренные последствия Ruby `begin... end` без` rescue`, которые используются в качестве кодового блока? - программирование
Подтвердить что ты не робот

Существуют ли непреднамеренные последствия Ruby `begin... end` без` rescue`, которые используются в качестве кодового блока?

Я иногда вижу блоки begin...end, используемые в ruby, без каких-либо rescue, else, ensure и т.д. операторов между ними. Например:

foo = begin
   whatever = 3
   "great"
   42
end

Целью кодера, похоже, является использование блока begin...end только для его качества группировки (как если бы begin были do). Лично я считаю, что это использование нарушает принцип наименьшего удивления (begin подразумевает обработку исключений).

Существуют ли какие-либо непреднамеренные последствия использования begin...end таким образом? У блоков begin...end есть какие-либо смысловые различия (возможно, в обработке исключений?), Которые делают это использование опасным?

Синтаксис Ruby невероятно тонкий, и я не удивлюсь, если бы здесь были странные gotchas.

4b9b3361

Ответ 1

Я использую это иногда, если я хочу присвоить что-то переменной, но мне нужно сначала вычислить значение, которое я хочу назначить. Это делает код немного более аккуратным таким образом. Я думаю, что это предпочтение пользователя. В основном вы говорите: я назначаю что-то foo, но для того, чтобы получить значение, которое я хочу, мне сначала нужно что-то сделать. Это особенно полезно при выполнении memoization, поэтому вместо

if @cache.nil?
  do_something!
  @cache = read_value
end

Вы можете сделать

@cache ||= begin
  do_something!
  read_value
end

То, что вы здесь используете, заключается в том, что интерпретатор Ruby имеет стек, и каждое выражение обычно толкает что-то в стеке или вынимает что-то из стека. Присвоение просто берет последнюю вещь из стека и присваивает ей (в этом случае последняя строка от начала/конца). Много раз зная это (стековый подход в Ruby) может быть полезным.

Я не думаю, что это нарушает наименьшее удивление, хотя, я думаю, это предпочтение пользователя, если вы хотите его использовать или нет.

Вы можете видеть, что это ничего не делает неожиданным, если посмотреть на то, что он написал в Ruby MRI 1.9:

 RubyVM::InstructionSequence::compile("c = begin; a = 5; 6; end").to_a

 [:trace, 1],
 [:trace, 1],
 [:putobject, 5],
 [:setlocal, 2],
 [:trace, 1],
 [:putobject, 6],
 [:dup],
 [:setlocal, 3],
 [:leave]

Трассировка предназначена только для отслеживания стека, вы можете игнорировать это. Dup дублирует последний элемент в стеке. В этом примере число локальной переменной a равно 2, а номер локальной переменной c равен 3 (следовательно, putobject, 2 назначит переменной a и т.д.). Единственным побочным эффектом этого по сравнению с a = 5; c = 6 является команда dup, что означает, что размер стека вашего метода будет больше на 1 слот. Но это не особенно важно, потому что это имеет какой-то эффект, когда интерпретатор находится внутри этого конкретного метода, а память для стека предварительно зарезервирована в любом случае, поэтому это означает, что указатель стека будет уменьшен на 1 больше, чем в противном случае. Так что вообще никаких изменений вообще нет. При включении оптимизаций даже dup, вероятно, исчезнет.