Ruby的“Open Classes”打破了封装吗?

时间:2010-11-15 13:29:17

标签: ruby encapsulation monkeypatching

在Ruby中,程序员可以更改预定义的类。所以一个非常糟糕的程序员可以做类似的事情:

class String
  def ==(other)
    return true
  end
end

显然,几乎没有人会这么愚蠢,但是对于预定义类的更微妙的改变可能会导致已经工作的代码出现问题的想法在我看来违反了封装原则。

四个问题:

  1. 首先,这实际上是否违反了OO封装原则?
  2. 其次,作为程序员,我有什么方法可以保证 我的代码是我正在使用未经修改的类版本吗?
  3. 第三,我是否应该在我的代码中“打开”任何类 原因是什么?
  4. 最后,在大规模的生产编码环境中如何处理这类事情?换句话说,编程行业的人们是否真的在其他人的代码中这样做 将使用?或者即使他们不这样做,你如何确保一些 插件作者某处并没有做到这样的事情 会破坏你计划的重要部分吗?
  5. 我知道这是一个有点主观的问题,但我真的很想知道更广泛的编程社区对这个所谓的“猴子修补”的看法。

6 个答案:

答案 0 :(得分:10)

  

首先,这实际上是否违反了OO封装原则?

  

第二,作为一名程序员,我是否有办法在我的代码中保证我正在使用未修改版本的类?

还没有。 Ruby 2.0中的类框(希望)将成为解决方案。

  

第三,我是否应该出于任何原因在我的代码中“打开”类?

仅作为最后的手段。

你应该从不猴子修补自己的课程。没有意义。你控制它们,你可以让它们一开始就做你想做的事。

你永远不应该在库中修补类。 (这个规则的例外是库唯一目的它是猴子补丁的东西,例如Marc-AndréLafortune的backports库,它修补了Ruby 1.8.6,1.8.7 ,1.9.0和1.9.1尽可能使用Ruby 1.9.2中的功能。)你可能提供一个附加库,它提供了猴子补丁,使你更容易使用你的库(例如,您有一个提供Kryptonite.encrypt(str)方法的加密库,并且您提供了一个附加String#encrypt方法),但该附加组件应该在一个单独的库中,用户需要< em>明确 require。它应该是完全可选的。

你不应该修补核心类。这指的是Ruby中的类ArraySymbol,但是对于Rails库,我还会在“核心”标签下包含类似ActiveRecord::Base的类。 (与上面的注意事项相同。例如,在3.0之前的Rails版本中,没有明确定义的插件API,猴子修补是唯一的方式来扩展Rails。如果没有违反此规则的人,那么从来没有任何插件,Rails永远不会是现在的位置。)

首先尝试继承。首先尝试合成(包装,代理,外观,适配器......)。首先尝试重构。首先尝试辅助对象。 如果不起作用,请转到猴子修补。

当你修补猴子时要尊重:如果你要添加一个新方法,请确保它不存在,如果有,请处理它(例如从你的方法调用它)。如果你要包装一个现有的方法,请确保如果其他人已经包装它,它们的包装器会被调用,并且当有人想要在之后包装它时,你的包装器允许这样做。 (特别是,这意味着您必须保留方法的现有合同。)

如果可能的话,将你的猴子补丁放入混合物中。这样,它们就会出现在继承链中,这将使任何试图调试代码的人有机会弄清楚发生了什么。将你的猴子补丁放在单独的,明显命名的文件中。

  

最后,在大规模的生产编码环境中如何处理这类事情?换句话说,编程行业的人们是否真的在其他人会使用的代码中执行此操作?或者即使他们不这样做,你如何确保某个插件作者在某个地方没有这样做会破坏你程序的重要部分?

不要像“非常糟糕的程序员”一样工作,就像你打电话一样。

这听起来很简单,这基本上归结为它。是的,当然,您可以编写测试,执行代码审查,练习结对编程,使用静态分析工具,在启用警告的情况下运行代码(例如,您在问题中发布的代码将生成warning: method redefined; discarding old ==)。但是对我而言,无论如何,这都是一个非常糟糕的程序员所做的事情。

答案 1 :(得分:4)

  1. 在某些情况下是的。如果您遵循一个类的范例负责一个作业和一个作业,那么重新开放类的使用通常(但不一定)打破封装。似乎这不是红宝石的传统。例如,Array类充当列表,数组和堆栈,因此stdlib似乎也不遵守严格的封装。我想是品味的问题。
  2. 我不知道。也许其他人会想出一些东西。
  3. 我的意见是,如果您正在编写其他人将使用的库,我会避免这样做。如果你正在编写一个应用程序并且需要来了(很简单的例子:你需要一个数字数组的平均方法 - 它是增加可读性而不是monkeypatching之间的选择)我会选择它。
  4. 最着名的现实世界monkeypatcher是rails。因此,经常记录核心类的特别好的变化是很好的。是的测试有帮助。

答案 2 :(得分:3)

  

首先,这实际上违反了   OO封装原理?

封装可以隐藏实现细节,而不是规定应该如何使用类。在ruby中,您通常会尊重私有变量,当您想要解决时,您知道自己在做什么是在升级库时可能会破坏的黑客攻击。我会说〜90%的时间我会打破封装是在测试情况下,当我不能用其他语言这样做时我觉得很烦人

  

第二,有没有办法,作为一个   程序员,我可以保证在我的   我正在使用的代码   一个类的未修改版本?

这会违反整个“开放阶级”的事情,不会吗; - )

  

第三,我应该“开放”吗?   我的代码中的类,出于任何原因?

将其视为“最后的手段”类型的东西。通常答案是“否”,因为您控制类定义不应该是必需的。将内容添加到特定实例的单例类中是一个完全不同的故事; - )

  

最后,这是怎么回事   处理大规模,生产   编码环境?换句话说,做   编程行业的人   实际上在其他人的代码中这样做   将使用?或者即使他们不这样做,如何   你确保一些插件作者吗?   某个地方没有做类似的事情   这将破坏一个重要的部分   你的程序?

作为一项规则,如果一个图书馆正在开放另一个图书馆,它应该作为最后的手段(即你不能通过普通的OO功能完成同样的事情),当他们这样做时,他们应该确定它尚未被其他人打开过。你可以做一些技巧来使这个过程更安全,比如旧的alias_method_chain,或者使用mixins和调用super的新东西。

话虽如此,在ruby中一直发生的事情,在rails中你就是如何获得插件的。

我在一个250k loc代码库的产品上工作,我们已经修补了很多东西。我们还练习TDD(在loc中以1:1.5的比例测试loc),并在提交到主线存储库之前运行所有测试。所有的猴子补丁都在文件中,其目的在“config / initializers”中清楚标明,并且所有这些补丁都经过了全面测试。已经在那里工作了一年,至少在那段时间我们从未遇到过与猴子补丁相关的问题。

话虽如此,它是我参演过的最好的团队,我们都非常致力于极限编程。如果其中任何一个不是这种情况,我认为rails不是一个好主意。你需要相信你的团队用一种与红宝石一样强大的语言来做正确的事情,并且尽可能多地进行检查和平衡。

答案 3 :(得分:2)

  1. 简短回答:是的。更长的回答:那种。封装的确是为了防止这种事情发生,但是封装可以在其他语言中被侵犯,尽管通过更困难的手段。

  2. 测试用例,或许,但同样,Ruby在编写应用程序时出现怪癖是臭名昭着的,尤其是在使用像Rails这样的重型框架时,这种框架因污染全局命名空间而导致犯罪,并且在版本3之前的意外情况下会导致奇怪的结果出来了。

  3. 我不确定你的意思是什么。

  4. 在现实世界中,开发人员决定使用哪些软件包,最好是经过严格测试的软件包。

  5. 另外,其他开发人员可以并经常打破他们使用的程序。封装不是锁定对应用程序部分访问的软件功能,它是一种语言功能,有助于防止编码人员直接搞乱您的代码。

答案 4 :(得分:1)

我目前的Ruby经验告诉我:

  • 是的,因为您可以添加一个方法来返回外部类的私有属性:程序可以随意破解封装。
  • 不,你无法阻止它,这是一种语言功能。
  • 是的,有时它看起来很有用,或者至少会产生好看的代码来向现有类添加方法:例如,将应用过滤方法添加到String或Array。无论如何,在模块中创建这些方法并包含这些方法。我特别喜欢它在ActiveRecord中完成的方式,阅读它们的来源,它们都很干净。
  • 在大型代码中,除非你有良好的单元测试和规范的开发人员,否则请考虑转换为不那么脆弱的语言(是的,我知道你们中的一些人会不同意)。

答案 5 :(得分:0)

对于第4部分,有“选择不被打破”的原则。如果很多人都在使用你正在使用的插件,那么如果一个插件做坏事,那么有人就会发现它。

然后,你可以使用其他人没有插件的组合。

相关问题