Как работает instance_eval и почему DHH его ненавидит? - PullRequest
21 голосов
/ 18 июня 2010

Примерно в 19:00 в его презентации на RailsConf , Дэвид Хайнемайер Ханссон рассказывает о недостатках instance_eval:

В течение долгого времени я бился и бредилпротив instance_eval, которая заключается в том, что не нужно использовать заданный параметр (например, do |people|) и просто прямо do something, а затем оценивать, что находится в этом блоке в рамках того, откуда вы пришли (я даже не знаю,это логичное объяснение)

Долгое время мне это не нравилось, потому что в каком-то смысле это казалось более сложным.Если вы хотите поместить свой собственный код туда, вы собираетесь запустить что-то, что уже было?Ты собирался что-то переопределить?Когда вы выдаете определенную переменную, вы можете связать все, что угодно, и вы можете знать, что [вы] не вмешиваетесь в чужие дела

Это звучит интересно, но а) Я не знаюкак в первую очередь работает instance_eval и б) я не понимаю, почему это может быть плохо / увеличивать сложность.

Может кто-нибудь объяснить?

Ответы [ 3 ]

31 голосов
/ 18 июня 2010

То, что делает instance_eval, это то, что он запускает блок в контексте другого экземпляра. Другими словами, он меняет значение self, что означает, что он меняет значение методов экземпляра и переменных экземпляра.

Это создает когнитивное разъединение: контекст, в котором выполняется блок, не является контекстом, в котором он появляется на экране.

Позвольте мне продемонстрировать это с небольшим изменением примера @Matt Briggs. Допустим, мы создаем электронное письмо вместо формы:

def mail
  builder = MailBuilder.new
  yield builder
  # executed after the block 
  # do stuff with builder 
end

mail do |f|
  f.subject @subject
  f.name    name
end

В этом случае @subject является переменной экземпляра вашего объекта, а name является методом вашего класса. Вы можете использовать красивую объектно-ориентированную декомпозицию и сохранить свой предмет в переменной.

def mail &block
  builder = MailBuilder.new
  builder.instance_eval &block
  # do stuff with builder 
end

mail do 
  subject @subject
  name    name # Huh?!?
end

В в этом случае @subject является переменной экземпляра объекта mail builder ! Это может даже не существовать ! (Или, что еще хуже, может существовать и содержать какое-то совершенно глупое значение.) не может получить доступ к переменным экземпляра вашего объекта. И как вы даже вызываете метод name вашего объекта? Каждый раз, когда вы пытаетесь вызвать его, вы получаете метод *1036* почтового сборщика.

По сути, instance_eval затрудняет использование вашего собственного кода внутри кода DSL. Поэтому его следует использовать только в тех случаях, когда вероятность того, что это понадобится, очень мала.

17 голосов
/ 18 июня 2010

Хорошо, так что идея здесь вместо чего-то вроде этого

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. перед вашими методами?

0 голосов
/ 19 июня 2010

Идея в том, что это немного опасно, потому что вы никогда не можете быть уверены, что не собираетесь что-то сломать, не читая весь код, который имеет дело с объектом, на котором вы используете instance_eval.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...