Ruby 中的“猴子补丁”到底是啥意思?

Posted

技术标签:

【中文标题】Ruby 中的“猴子补丁”到底是啥意思?【英文标题】:What does 'Monkey Patching' exactly Mean in Ruby?Ruby 中的“猴子补丁”到底是什么意思? 【发布时间】:2010-09-28 11:49:02 【问题描述】:

根据***,monkey patch 是:

一种扩展或修改运行时的方法 动态语言代码 [...] 在不改变原始来源的情况下 代码。

同一条目中的以下陈述使我感到困惑:

在 Ruby 中,术语猴子补丁是 被误解为任何动态 修改一个类,通常是 用作动态的同义词 在运行时修改任何类。

我想知道Ruby中猴子补丁的确切含义。是在做类似下面的事情,还是做其他事情?

class String
  def foo
    "foo"
  end
end

【问题讨论】:

【参考方案1】:

这是猴子补丁:

class Float
  def self.times(&block)
    self.to_i.times  |i| yield(i) 
    remainder = self - self.to_i
    yield(remainder) if remainder > 0.0
  end
end

现在我想这有时可能很有用,但想象一下,如果你看到了常规。

def my_method(my_special_number)
  sum = 0
  my_special_number.times  |num| sum << some_val ** num 
  sum
end

它只是在被调用时偶尔中断。对于那些关注你的人来说你已经知道为什么了,但是想象一下你不知道浮点类型有一个.times 类方法并且你自动假设my_special_number 是一个整数。每次参数是整数、整数或浮点数时,它都可以正常工作(除非有浮点余数,否则会传回整数)。但是传入一个小数区域内的数字,它肯定会坏掉!

想象一下,您的 gem、Rails 插件,甚至您自己的项目中的同事,这种情况发生的频率有多高。如果里面有一两个这样的小方法,可能需要一些时间来查找和纠正。

如果您想知道它为什么会中断,请注意 sum 是一个整数,并且可以将浮点余数传回;此外,指数符号仅在类型相同时才有效。所以你可能认为它是固定的,因为你将麻烦的数字转换为浮点数......结果发现总和不能取浮点结果。

【讨论】:

虽然猴子补丁并不总是一个好主意,但您肯定不需要几个月的时间来找到错误的根源,因为它会出现在堆栈中。 是的,但是想象一下它在一个大型的 Rails Web 应用程序中,它在 99% 的时间里都能正常运行。现在您必须梳理日志以找出失败的原因,这可能需要大量工作。 ;-) 这是一个很好的真实例子,说明为什么猴子修补是危险的 任何方法中的错误都需要梳理日志。这与“猴子补丁”无关。在 C# 中,您可以以几乎相同的方式扩展类,并且没有人对此抱怨。抱怨“猴子补丁”只是大多数人不加思索地重复的模因。也就是说,我同意@emery 的观点,因为猴子修补 like this 是危险的,因为它改变了现有的标准化方法,这意味着即使其中没​​有错误并且毫无戒心的程序员可能会使用它,它的行为也可能有所不同它以错误的方式。一个好主意是让您的 IDE 标记此类方法。【参考方案2】:

你是对的;当您修改或扩展现有类而不是对其进行子类化时。

【讨论】:

【参考方案3】:

通常是关于临时更改,使用 Ruby 开放类,通常使用低质量代码。

对该主题的良好跟进:

http://www.infoq.com/articles/ruby-open-classes-monkeypatching

【讨论】:

【参考方案4】:

简短的回答是没有“确切”的含义,因为它是一个新颖的术语,不同的人使用它的方式也不同。至少可以从***的文章中看出这一点。有些人坚持认为它只适用于“运行时”代码(我想是内置类),而有些人会用它来指代任何类的运行时修改。

就个人而言,我更喜欢更具包容性的定义。毕竟,如果我们只使用修改内置类的术语,我们如何指代所有其他类的运行时修改?对我来说重要的是源代码和实际运行的类之间存在差异。

在 Ruby 中,术语猴子补丁是 被误解为任何动态 修改一个类,通常是 用作动态的同义词 在运行时修改任何类。

上述声明断言 Ruby 的用法不正确 - 但术语会不断发展,这并不总是一件坏事。

【讨论】:

"...不同的人使用它的方式不同。"在阅读了此处和 Ruby-Talk 邮件列表中的答案后,我意识到您是绝对正确的。该术语在首次创造时具有更具体的含义,但是 - 正如您所说 - “术语不断发展,这并不总是一件坏事。”【参考方案5】:

Monkey patching 是指您在运行时替换 类的方法(不是像其他人描述的那样添加新方法)。

除了是一种非常不明显且难以调试的代码更改方式之外,它还无法扩展;随着越来越多的模块开始使用猴子修补方法,更改相互影响的可能性越来越大。

【讨论】:

没错,但是我展示的是一种相似的方法,而不是一种全新的方法。 那么描述添加新方法的行为的术语是什么(如果有这样的术语)? 如果两个不同的库将相同的方法添加到同一个类中,那么一个总是会替换另一个。两者都只是 adding 方法并不重要。添加仍然是猴子补丁。至少,Ruby 社区是这样使用这个术语的。 Python 以不同的方式使用它。【参考方案6】:

在 Python 中,monkeypatching 经常被称为尴尬的标志:“我不得不对这个类进行monkeypatch 因为......”(我在处理 Zope 时首先遇到了它,文章提到了这一点)。过去常说有必要抓住上游类并在运行时修复它,而不是游说在实际类中修复不需要的行为或在子类中修复它们。以我的经验,Ruby 的人很少谈论猴子补丁,因为它并不被认为特别糟糕甚至值得注意(因此是“打鸭子”)。显然,您必须小心更改将在其他依赖项中使用的方法的返回值,但是以 active_support 和 facets 的方式向类添加方法是非常安全的。

10 年后更新:我将最后一句修改为“相对安全”。如果其他人有相同的想法并添加具有不同实现或方法签名的相同方法,或者如果人们混淆了核心语言功能的扩展方法,则使用新方法扩展核心库类可能会导致问题。这两种情况都经常发生在 Ruby 中(尤其是关于 active_support 方法)。

【讨论】:

这是有道理的。 Python 在一般情况下遵循最少意外和最少魔法的原则更加自律,因此更改内置函数以具有新的合约或行为被认为有点粗鲁。然而,Ruby 更倾向于简洁,偶尔会牺牲一些额外的魔力。 Ruby 中有很多东西永远不会被 Python 的决策过程所接受,无论好坏(优点:没有隐式返回。缺点:没有匿名函数等)【参考方案7】:

我听到的关于 Monkey patching/Duck-punching 的最佳解释是 Patrick Ewing 在RailsConf 2007

...如果它像鸭子一样走路和像鸭子一样说话,那它就是鸭子,对吧?所以 如果这只鸭子没有给你想要的噪音,你必须 打那只鸭子,直到它返回您期望的结果。

【讨论】:

“猴子补丁就像暴力。如果它不起作用,你就没有使用足够的它。” 用 web.archive.org 快照更新了链接。【参考方案8】:

无代码概念解释:

这意味着您可以动态修改代码。想要动态地将方法添加到仅在运行时才知道的特定类?没问题。它很强大,是的:但可能会被滥用。这个概念可能有点太深奥而难以理解,所以我在下面准备了一个不使用任何代码的示例:

如何修补汽车:

普通汽车操作

您通常如何启动汽车?很简单:你转动点火开关,汽车就启动了!

很好,但是我们如何“猴子补丁”汽车类?

这就是 Fabrizzio 对可怜的 Mikey 所做的。通常,如果您想更改汽车的运行方式,则必须在汽车制造厂(即在 Car 类中)进行这些更改。 Fabrizzio 没有时间这样做:他偷偷摸摸地从引擎盖下修补汽车,偷偷地重新布线。换句话说,他重新打开 Car 类,进行他想要的更改,然后他就完成了:他只是给汽车打了补丁。

在进行猴子补丁时,您必须真正知道自己在做什么,否则结果可能会非常爆炸性。

法布里齐奥,你要去哪里?

轰隆隆!

如孔子所说:

“保持你的源代码接近,但你的猴子补丁更接近。”

这可能很危险。

【讨论】:

以上是关于Ruby 中的“猴子补丁”到底是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

ruby 因为没人喜欢猴子补丁。

ruby 猴子补丁模式

ruby 猴子补丁模式

php中的猴子补丁

ruby 猴子补丁用keen.io保存查询

寻找可能导致麻烦的 Ruby 猴子补丁的来源?