instance_eval是如何工作的以及为什么DHH讨厌它?

Tom*_*man 21 ruby ruby-on-rails

他的RailsConf演讲中大约19:00 ,David Heinemeier Hansson谈到了以下方面的缺点instance_eval:

很长一段时间我咆哮并且反对instance_eval,这是不使用屈服参数(如do |people|)的概念,只是直接do something然后评估你来自哪个范围内的那个块里的东西(我甚至不知道那是不是连贯的解释)

很长一段时间我都不喜欢这样,因为从某种意义上说它感觉更复杂.如果你想把你自己的代码放在那里你会触发已经存在的东西吗?你要覆盖一些东西吗?当你产生一个特定的变量时,你可以将所有东西都链接起来,你可以知道[你]并没有搞乱其他人的东西

这听起来很有趣,但是a)我不知道如何instance_eval在第一时间起作用和b)我不明白为什么它可能是坏的/增加复杂性.

谁能解释一下?

Jör*_*tag 31

这样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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,@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
Run Code Online (Sandbox Code Playgroud)

这种情况下,@subject邮件构建器对象的实例变量!它甚至可能不存在!(或者更糟的是,它可能存在并包含一些完全愚蠢的值.)有没有办法让你可以访问你的对象的实例变量.你怎么称呼name对象的方法呢?每次尝试调用它时,都会获得邮件构建器的方法.

基本上,instance_eval很难在DSL代码中使用自己的代码.因此,它实际上应该仅用于可能需要这种情况的机会.


Mat*_*ggs 17

好的,所以这里的想法不是这样的

form_for @obj do |f|
  f.text_field :field
end
Run Code Online (Sandbox Code Playgroud)

你得到这样的东西

form_for @obj do 
  text_field :field
end
Run Code Online (Sandbox Code Playgroud)

第一种方式非常简单,你最终会得到一个看起来像这样的模式

def form_for
  b = FormBuilder.new
  yield b
  b.fields.each |f|
    # do stuff
  end
end
Run Code Online (Sandbox Code Playgroud)

你产生了一个构建器对象,消费者在其上调用方法,然后调用构建器对象上的方法来实际构建表单(或其他)

第二个更神奇

def form_for &block
  b = FormBuilder.new
  b.instance_eval &block
  b.fields.each |f|
    #do stuff
  end
end
Run Code Online (Sandbox Code Playgroud)

在这一个中,我们不是将构建器放到块中,而是在构建器的上下文中对块进行评估

第二个增加了复杂性,因为你喜欢玩范围游戏,你需要了解它,消费者需要理解这一点,而编写你的构建者的人需要理解这一点.如果每个人都在同一个页面上,我不知道这在某种程度上是一件坏事,但我确实质疑成本与成本之间的关系,我的意思是,仅仅强调f是多么困难.在你的方法面前?