9

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

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

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

四个问题:

  1. 首先,这实际上是否违反了封装的 OO 原则?
  2. 其次,作为一名程序员,有没有办法可以在我的代码中保证我正在使用一个未修改版本的类?
  3. 第三,出于任何原因,我是否应该在我的代码中“打开”类?
  4. 最后,在大规模的生产编码环境中如何处理这类事情?换句话说,编程行业的人真的会在其他人会使用的代码中这样做吗?或者即使他们不这样做,您如何确保某处的某个插件作者没有做这样的事情会破坏您程序的重要部分?

我知道这是一个有点主观的问题,但我真的很想知道更广泛的编程社区对所谓的“猴子补丁”的看法。

4

6 回答 6

10

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

是的。

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

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

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

只能作为最后的手段。

永远不应该对自己的课程进行修补。根本没有意义。你控制他们,你可以让他们首先做你想做的事。

您永远不应该在库中修补类。(这个规则的例外是那些唯一目的是对某些东西进行猴子补丁的库,例如 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方法),但该插件应该在一个单独的库中,用户需要明确地 require. 它应该是完全可选的。

你不应该猴子补丁核心类。这指的是RubyArray或Ruby 中的类,但对于 Rails 库,我还会在“核心”标签下Symbol包含类。ActiveRecord::Base(与上述相同的警告。例如,在 3.0 之前的 Rails 版本中,没有明确定义的插件 API,猴子补丁是扩展 Rails 的唯一方法。如果没有人违反这条规则,就不会有任何插件,并且Rails 永远不会像现在这样。)

先试试继承。首先尝试组合(包装器、代理、外观、适配器……)。先尝试重构。首先尝试辅助对象。只有当这不起作用时,才转向猴子补丁。

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

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

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

不要与你所说的“非常糟糕的程序员”一起工作。

听起来很简单,但基本上就是这样。是的,当然,您可以编写测试、进行代码审查、练习结对编程、使用静态分析工具、在启用警告的情况下运行您的代码(例如,您在问题中发布的代码将生成一个warning: method redefined; discarding old ==. 但对我来说,这都是一个不是很差的程序员无论如何都会做的事情。

于 2010-11-15T16:58:55.247 回答
4
  1. 在某些情况下是的。如果您遵循一个班级负责一份工作和一份工作的范式,那么重新开放班级的使用通常(尽管不一定)会破坏封装。然而,这似乎不是红宝石的传统。例如,Array 类充当列表、数组和堆栈,因此 stdlib 似乎也不遵守严格封装。我猜是口味问题。
  2. 我不知道有什么办法。也许其他人会想出一些东西。
  3. 我的观点是,如果你正在编写一个其他人会使用的库,我会避免这样做。如果您正在编写一个应用程序并且需要(简单的示例:您需要为数字数组提供一个均值方法 - 这是在增加可读性和不猴子补丁之间进行选择)我会去的。
  4. 最(不)著名的现实世界猴子补丁是rails。所以通常最好记录对核心类的特别好的更改。是的,测试会有所帮助。
于 2010-11-15T13:47:10.933 回答
3

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

封装是为了隐藏实现细节,而不是规定应该如何使用一个类。在 ruby​​ 中,您通常尊重私有变量,并且当您想要绕过这一点时,您知道自己在做什么是一个 hack,当您升级库时可能会中断。我会说大约 90% 的时间我会打破封装是在测试情况下,当我不能用其他语言做到这一点时,我觉得这很烦人

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

那会违反整个“公开课”的事情,不是吗;-)

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

将其视为“最后的手段”类型的事情。通常答案是“不”,因为您控制了类定义,因此不需要这样做。不过,向特定实例的单例类添加东西完全是另一回事;-)

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

作为一项规则,如果一个库正在打开另一个库,它应该作为最后的手段来完成(即你不能通过正常的 OO 功能完成同样的事情),当他们这样做时,他们应该确保它没有已经被别人打开了。您可以采取一些技巧来使流程更安全,例如旧的 alias_method_chain,或者使用 mixins 和调用 super 的新东西。

话虽如此,在 ruby​​ 中一直在发生,在 Rails 中,它是你获取插件的方式。

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

话虽如此,它是我工作过的最好的团队,我们都非常致力于极限编程。如果不是其中任何一个,我认为 Rails 不是一个好主意。你需要相信你的团队能够用一种像 ruby​​ 一样强大的语言来做正确的事情,并尽可能多地进行制衡。

于 2010-11-15T14:29:53.613 回答
2
  1. 简短的回答:是的。更长的答案:有点。封装的目的确实是为了防止这种事情发生,但是在其他语言中可能会违反封装,尽管通过更困难的方式。

  2. 也许是测试用例,但同样,Ruby 因编写应用程序时的怪癖而臭名昭著,尤其是在使用像 Rails 这样的重型框架时,它会污染全局命名空间并在意外情况下导致奇怪的结果,直到版本 3 发布。

  3. 我不确定你这个问题是什么意思。

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

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

于 2010-11-15T13:40:32.293 回答
1

我目前对 Ruby 的体验告诉我:

  • 是的,因为您可以添加一个方法来返回外部类的私有属性:程序可以随意破坏封装。
  • 不,您无法阻止这种情况,这是一种语言功能。
  • 是的,有时它看起来很有用,或者至少可以生成好看的代码来向现有类添加方法:例如向字符串或数组添加应用过滤方法。无论如何,在模块中创建这些方法并包含它们。我特别喜欢它在ActiveRecord中完成的方式,阅读他们的源代码,一切都很好而且很干净。
  • 在大规模代码中,除非您有良好的单元测试和训练有素的开发人员,否则请考虑切换到不那么脆弱的语言(是的,我知道你们中的一些人会不同意)。
于 2010-11-15T13:46:41.913 回答
0

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

再说一次,您可能会使用其他人没有的插件组合。

于 2010-11-16T12:02:07.810 回答