Как работает instance_eval и почему DHH ненавидит его?
Ответ 1
То, что делает instance_eval
, заключается в том, что он запускает блок в контексте другого экземпляра. Другими словами, он изменяет значение self
, что означает, что он изменяет значение методов экземпляра и переменных экземпляра.
Это создает когнитивное отключение: контекст, в котором выполняется блок, не является контекстом, в котором он отображается на экране.
Позвольте мне продемонстрировать это с небольшим изменением примера @Matt Briggs. Скажем, мы не создаем форму, мы создаем электронное письмо:
mail do |f|
f.subject @subject
f.name name
end
В этом случае @subject
является переменной экземпляра вашего объекта, а name
- это метод вашего класса. Вы можете использовать красивую объектно-ориентированную декомпозицию и сохранить свой объект в переменной.
mail do
subject @subject
name name # Huh?!?
end
В этом случае @subject
является переменной экземпляра объекта почтового строителя! Это может даже не существовать! (Или, что еще хуже, он может существовать и содержать какое-то совершенно глупое значение.) Вы не можете получить доступ к переменным экземпляра объекта. И как вы даже называете метод name
вашего объекта? Каждый раз, когда вы пытаетесь вызвать его, вы получаете метод почтового построителя.
В принципе, instance_eval
затрудняет использование собственного кода внутри DSL-кода. Поэтому он действительно должен использоваться только в тех случаях, когда очень мало шансов, что это может понадобиться.
Ответ 2
Итак, идея здесь вместо чего-то вроде этого
form_for @obj do |f|
f.text_field :field
end
вы получите что-то вроде этого
form_for @obj do
text_field :field
end
первый способ довольно прямолинейный, вы получаете шаблон, похожий на этот
def form_for
b = FormBuilder.new
yield b
b.fields.each |f|
# do stuff
end
end
вы получаете объект-строитель, который потребитель вызывает методы, а затем вы вызываете методы в объекте-компоновщике, чтобы фактически создать форму (или что-то еще)
второй - немного более магический
def form_for &block
b = FormBuilder.new
b.instance_eval &block
b.fields.each |f|
#do stuff
end
end
в этом, вместо того, чтобы давать строителю блок, мы берем блок и оцениваем его в контексте компоновщика
Во-вторых, вы увеличиваете сложность, потому что вы являетесь игровыми играми с областью действия, вам нужно понять это, и потребитель должен это понимать, и тот, кто написал ваш строитель, должен это понимать. Если все находятся на одной странице, я не знаю, что это определенно плохо, но я сомневаюсь в преимуществах по сравнению с расходами, я имею в виду, как трудно просто зацепиться за f. перед вашими методами?
Ответ 3
Идея состоит в том, что это немного опасно в том, что вы никогда не можете быть абсолютно уверены, что не собираетесь что-то сломать, не читая весь код, связанный с объектом, использующим instance_eval.
Также, если вы, скажем, обновили библиотеку, которая не сильно изменила интерфейс, но изменила многие внутренние объекты объекта, вы действительно можете нанести какой-то ущерб.