【问题标题】:Do Ruby's "Open Classes" break encapsulation?Ruby 的“开放类”会破坏封装吗?
【发布时间】:2011-05-10 05:56:35
【问题描述】:

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

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

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

四个问题:

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

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

【问题讨论】:

    标签: ruby encapsulation monkeypatching


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      首先,这是否违反了封装的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。它应该是完全可选的。

      您不应该对核心类进行猴子补丁。这指的是像 Ruby 中的 ArraySymbol 这样的类,但是对于 Rails 库,我还会在“核心”标签下包含像 ActiveRecord::Base 这样的类。 (与上述相同的警告。例如,在 3.0 之前的 Rails 版本中,没有明确定义的插件 API,monkey-patching 是扩展 Rails 的唯一方法。没有违反此规则的人,将从来没有任何插件,Rails 也不会是现在的样子。)

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

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

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

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

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

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

      【讨论】:

        【解决方案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​​ 一样强大的语言做正确的事情,并尽可能多地进行制衡。

        【讨论】:

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

          【讨论】:

          • 在 2: 也许像 String.freeze 。
          【解决方案5】:

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

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

          【讨论】:

          • 虽然我是一个 ruby​​ fanboi,但我完全同意最后的说法。如果您有糟糕的开发人员,请使用 java 或 c#。 ruby 和 python 只有在每个人都知道自己的手艺足以做出正确选择时才有意义。
          【解决方案6】:
          1. 简短回答:是的。更长的答案:有点。封装的目的确实是为了防止这种事情发生,但是封装可以在其他语言中被违反,尽管通过更困难的方式。

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

          3. 我不知道你这个问题是什么意思。

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-05-14
            • 2013-04-16
            • 2012-11-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-11-03
            相关资源
            最近更新 更多