24

像 Ruby 和 JavaScript 这样的一些语言有开放的类,允许你修改核心类的接口,比如数字、字符串、数组等。显然这样做可能会使其他熟悉 API 的人感到困惑,但是否有充分的理由避免它,假设您要添加到界面而不更改现有行为?

例如,将Array.map实现添加到不实现 ECMAScript 第 5 版的 Web 浏览器(如果您不需要所有 jQuery)可能会很好。或者您的 Ruby 数组可能会受益于使用“inject”的“sum”便捷方法。只要更改与您的系统隔离(例如,不是您为分发而发布的软件包的一部分),是否有充分的理由不利用此语言功能?

4

6 回答 6

29

猴子补丁,就像编程工具箱中的许多工具一样,既可以用于善也可以用于恶。问题是,总的来说,这些工具最常被使用的地方。根据我使用 Ruby 的经验,天平在“邪恶”方面占了很大比重。

那么猴子补丁的“邪恶”用途是什么?好吧,猴子修补通常会让你对重大的、潜在的无法诊断的冲突敞开心扉。我有一堂课A。我有某种猴子修补模块MB,可以修补A包括method1,method2method3. 我有另一个猴子补丁模块MC,它也补丁A包含一个method2,method3method4. 现在我陷入了困境。我打电话instance_of_A.method2:谁的方法被调用?答案可能取决于很多因素:

  1. 我是按什么顺序引入补丁模块的?
  2. 补丁是立即应用还是在某种有条件的情况下应用?
  3. 啊啊啊!蜘蛛正在从里面吃掉我的眼球!

好的,所以#3可能有点过于戏剧化了......

无论如何,这就是猴子修补的问题:可怕的冲突问题。鉴于通常支持它的语言的高度动态性,您已经面临许多潜在的“远程幽灵行动”问题;猴子补丁只是增加了这些。

如果您是负责任的开发人员,可以使用猴子补丁是很好的。不幸的是,IME,往往会发生的情况是,有人看到猴子补丁并说:“太好了!我只会猴子补丁,而不是检查其他机制是否更合适。” 这种情况大致类似于 Lisp 代码库,这些代码库是由那些在考虑将宏作为一个函数来实现之前就使用它的人创建的。

于 2011-04-21T10:05:11.397 回答
22

维基百科对猴子修补的陷阱有一个简短的总结:

http://en.wikipedia.org/wiki/Monkey_patch#Pitfalls

任何事情都有时间和地点,猴子修补也是如此。经验丰富的开发人员掌握了许多技术,并学习何时使用它们。它本身很少是一种“邪恶”的技术,只是不加考虑地使用它。

于 2011-04-21T09:11:03.323 回答
4

只要更改与您的系统隔离(例如,不是您为分发而发布的软件包的一部分),是否有充分的理由不利用此语言功能?

作为一个孤立问题的独立开发人员,扩展或更改本机对象没有问题。同样在较大的项目中,这也是应该做出的团队选择。

就我个人而言,我不喜欢更改 javascript 中的本机对象,但这是一种常见的做法,也是一种有效的选择。如果您要编写一个供他人使用的库或代码,我会尽量避免使用它。

然而,允许用户设置一个配置标志是一个有效的设计选择,该标志说明请用您的便利方法覆盖本机对象,因为这样很方便。

说明一个 JavaScript 特定的陷阱。

Array.protoype.map = function map() { ... };

var a = [2];
for (var k in a) {
    console.log(a[k]);
} 
// 2, function map() { ... }

这个问题可以通过使用 ES5 来避免,它允许您将不可枚举的属性注入到对象中。

这主要是一个高级设计选择,每个人都需要意识到/同意这一点。

于 2011-04-21T09:22:37.377 回答
3

使用“猴子补丁”来纠正特定的已知问题是完全合理的,替代方法是等待补丁修复它。这意味着暂时承担修复某些事情的责任,直到有一个“适当的”、正式发布的修复程序可供您部署。

Gilad Bracha 关于猴子补丁的深思熟虑的意见:http: //gbracha.blogspot.com/2008/03/monkey-patching.html

于 2011-04-21T09:50:47.810 回答
3

关于Javascript:

假设您要添加到界面而不更改现有行为,是否有充分的理由避免它?

是的。最坏的情况是,即使您不更改现有行为,也可能会损坏该语言的未来语法。

这正是和发生的Array.prototype.flatten事情Array.prototype.contains。简而言之,为这些方法编写了规范,他们的提案进入了第 3 阶段,然后浏览器开始发布它。Array但是,在这两种情况下,都发现有一些古老的库用自己的方法修补了内置对象,这些方法与新方法的名称相同,并且具有不同的行为;结果,网站崩溃了,浏览器不得不退出他们对新方法的实现,并且不得不编辑规范。(这些方法已重命名。)

Array 如果您像在您自己的浏览器、您自己的计算机上那样改变一个内置对象,那很好。(这对于用户脚本来说是一种非常有用的技术。)如果您在面向公众的网站上改变一个内置对象,那就不太好 - 它最终可能会导致上述问题。如果你碰巧控制了一个大网站(比如 stackoverflow.com)并且你改变了一个内置对象,你几乎可以保证浏览器会拒绝实现破坏你网站的新功能/方法(因为那个浏览器的用户不会能够使用您的网站,他们将更有可能迁移到不同的浏览器)。(请参阅此处了解规范编写者和浏览器制造商之间的此类交互的解释)

所有这一切,关于你问题中的具体例子

例如,将 Array.map 实现添加到不实现 ECMAScript 第 5 版的 Web 浏览器可能会很好

这是一种非常常见且值得信赖的技术,称为polyfill

polyfill 是在不支持该功能的 Web 浏览器上实现该功能的代码。大多数情况下,它指的是实现 HTML5 Web 标准的 JavaScript 库,可以是旧浏览器上的既定标准(某些浏览器支持),也可以是现有浏览器上的提议标准(任何浏览器不支持)

例如,如果您编写了一个完全符合Stage 4 官方规范的polyfill for Array.prototype.map(或者,以较新的示例,for Array.prototype.flatMap),然后运行在尚未拥有它的浏览器上定义的代码:Array.prototype.flatMap

if (!Array.prototype.flatMap) {
  Array.prototype.flatMap = function(...
    // ...
  }
}

如果你的实现是正确的,这完全没问题,并且在整个网络上都很常见,这样过时的浏览器就可以理解更新的方法。polyfill.io是此类事情的常用服务。

于 2019-05-16T12:08:10.253 回答
2

您描述的条件——添加(不改变)现有行为,而不是向外界发布你的代码——似乎相对安全。但是,如果 Ruby 或 JavaScript 或 Rails 的下一版本更改了它们的 API,则可能会出现问题。例如,如果某个未来版本的 jQuery 检查 Array.map 是否已经定义,并假设它是 map 的 EMCA5Script 版本,而实际上它是你的猴子补丁怎么办?

同样,如果您在 Ruby 中定义“sum”,并且有一天您决定要在 Rails 中使用该 ruby​​ 代码或将 Active Support gem 添加到您的项目中,该怎么办。Active Support 还定义了 sum 方法(在 Enumerable 上),因此存在冲突。

于 2013-06-29T00:26:00.880 回答