“猴子补丁”真的那么糟糕吗?

时间:2011-04-21 09:04:54

标签: javascript ruby oop language-design monkeypatching

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

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

6 个答案:

答案 0 :(得分:24)

与编程工具箱中的许多工具一样,猴子修补可以用于善恶。问题是总的来说,这些工具往往最常用。根据我对Ruby的经验,平衡在很大程度上取决于“邪恶”方面。

那么猴子修补的“邪恶”用法是什么?好吧,猴子修补一般让你对主要的,可能不可识别的冲突敞开大门。我有一个班级A。我有一些猴子修补模块MB,可以修补A以包含method1method2method3。我还有另一个猴子修补模块MC,它还会修补A以包含method2method3method4。现在我陷入困境。我打电话给instance_of_A.method2:调用哪个方法?答案可能取决于很多因素:

  1. 我以哪种顺序引入了修补模块?
  2. 补丁是否适用于某种有条件的环境?
  3. AAAAAAARGH!蜘蛛正在从内部吃掉我的眼球!
  4. 好的,所以#3可能有点过于戏剧化......

    无论如何,这是猴子修补的问题:可怕的冲突问题。鉴于通常支持它的语言的高度动态性,你已经面临很多潜在的“远距离的怪异行动”问题;猴子修补只会增加这些。

    如果您是负责任的开发人员,那么可以使用猴子修补程序。不幸的是,IME,往往发生的事情是,有人看到猴子修补,并说,“甜!我只是猴子补丁,而不是检查是否其他机制可能不合适。”这种情况大致类似于Lisp代码库,这些代码库是由人们在考虑将其作为函数执行之前为宏创建的。

答案 1 :(得分:17)

维基百科简要总结了猴子修补的缺陷:

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

一切都有时间和地点,也适合猴子修补。经验丰富的开发人员可以掌握许多技术,并了解何时使用它们。它很少是一种技术本身就是“邪恶的”,只是不加思索地使用它。

答案 2 :(得分:4)

  

只要更改被隔离到   你的系统(例如不属于你的系统)   您发布的软件包   分配)是有充分理由的   不要利用这种语言   功能?

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

我个人不喜欢改变javascript中的原生对象,但这是一种常见的做法,这是一个有效的选择。如果你要编写一个意图供其他人使用的库或代码,我会大大避免使用它。

然而,允许用户设置配置标志是有效的设计选择,请使用便利方法覆盖本地对象,因为这样方便。

说明JavaScript特定的陷阱。

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

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

使用ES5可以避免此问题,ES5允许您将不可枚举的属性注入对象。

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

答案 3 :(得分:3)

使用“猴子修补”来纠正一个特定的已知问题是完全合理的,其中替代方案是等待补丁修复它。这意味着暂时承担修复工作的责任,直到您可以部署“正确”,正式发布的修补程序。

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:0)

关于Javascript:

  

假设您要添加到界面中而不更改现有行为,是否有充分的理由避免这种情况发生?

是的。最坏的情况是,即使不改变现有的 行为,也有可能破坏该语言的 future 语法。

这正是Array.prototype.flattenArray.prototype.contains发生的情况。简而言之,规范是针对这些方法编写的,他们的建议到达了stage 3,然后浏览器开始发布它。但是,在这两种情况下,都发现存在一些古老的库,它们用自己的方法与新方法同名修补内置Array对象,并且行为不同;结果,网站崩溃了,浏览器不得不退出对新方法的实现,并且必须对规范进行编辑。 (方法已重命名。)

如果您在自己的浏览器,您自己的计算机上对Array 这样的内置对象进行了变异,就可以了。 (这对于用户脚本来说是一种非常有用的技术。)如果您在面向公众的站点上对内置对象进行了变异,那就没那么好了-它可能最终会导致上述问题。如果您碰巧控制了一个大站点(例如stackoverflow.com),并且对内置对象进行了变异,则几乎可以保证浏览器将拒绝实施会破坏您站点的新功能/方法(因为那么该浏览器的用户将无法使用您的网站,并且他们更有可能迁移到其他浏览器)。 (请参见here,以获取规范作者与浏览器制造商之间这种相互作用的说明)

关于您问题中的特定示例的所有说明:

  

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

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

  

polyfill是在不支持该功能的Web浏览器上实现该功能的代码。通常,它是指一个JavaScript库,该库实现HTML5 Web标准,或者是旧版浏览器上的已建立标准(某些浏览器支持),或者是现有浏览器上的建议标准(不受任何浏览器支持)

例如,如果您为Array.prototype.map写了一个polyfill(或者为Array.prototype.flatMap写了一个较新的例子),而该文本与{{3 }},然后在尚没有Array.prototype.flatMap的浏览器上运行代码:

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

如果您的实现是正确的,那么这很好,并且通常在整个Web上都可以完成,因此过时的浏览器可以理解较新的方法。 official Stage 4 specification是这类事情的常用服务。