Подтвердить что ты не робот

Как работает instance_eval и почему DHH ненавидит его?

Примерно в 19:00 в

4b9b3361

Ответ 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.

Также, если вы, скажем, обновили библиотеку, которая не сильно изменила интерфейс, но изменила многие внутренние объекты объекта, вы действительно можете нанести какой-то ущерб.