Ruby 的“公开课”吗?打破封装?

发布于 2024-10-02 03:23:12 字数 526 浏览 0 评论 0原文

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

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

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

四个问题:

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

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

In Ruby, programmers are allowed to change predefined classes. So a really bad programmer could do something like:

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

Obviously, almost no one would be quite this dumb, but the idea that more subtle changes to a predefined class could cause problems in already-working code seems to me to violate the principle of encapsulation.

Four questions:

  1. First, does this, in fact, violate the OO principle of encapsulation?
  2. Second, is there a way, as a programmer, that I can guarantee in
    my code that I am working with an unmodified version of a class?
  3. Third, should I ever be "opening" classes in my code, for any
    reason?
  4. Finally, how is this sort of thing handled in a large-scale, production coding environment? In other words, do people in the programming industry actually do this in code that others
    will use? Or even if they don't, how do you ensure that some
    plugin author somewhere isn't doing something like this that
    will ruin an essential part of your program?

I know this is a somewhat subjective question, but I'd really like to know how the wider programming community feels about this so called "monkey patching."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

裸钻 2024-10-09 03:23:12

首先,这实际上违反了面向对象的封装原则吗?

是的。

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

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

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

只能作为最后的手段。

你不应该永远给你自己的类打补丁。根本没有意义。你控制他们,你可以让他们首先做你想做的事。

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

你不应该给核心类打猴子补丁。这指的是 Ruby 中的 ArraySymbol 之类的类,但对于 Rails 库,我还会在“核心”标签。 (与上面相同的警告。例如,在 3.0 之前的 Rails 版本中,没有明确定义的插件 API,猴子补丁是扩展 Rails 的唯一方法。如果没有人违反此规则,就会有从来没有任何插件,Rails 也永远不会达到现在的水平。)

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

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

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

最后,在大规模的生产编码环境中如何处理这种事情?换句话说,编程行业的人们真的会用其他人会使用的代码来做到这一点吗?或者即使他们不这样做,您如何确保某个地方的某些插件作者不会做这样的事情,这会毁掉您程序的重要部分?

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

尽管这听起来很简单,但基本上就是这样。是的,当然,您可以编写测试,进行代码审查,练习结对编程,使用静态分析工具,在启用警告的情况下运行代码(例如,您在问题中发布的代码将生成一个警告:方法重新定义;丢弃旧==)。但对我来说,这都是一个不太糟糕的程序员会做的事情。

First, does this, in fact, violate the OO principle of encapsulation?

Yes.

Second, is there a way, as a programmer, that I can guarantee in my code that I am working with an unmodified version of a class?

Not yet. Classboxes in Ruby 2.0 are (hopefully) going to be the solution.

Third, should I ever be "opening" classes in my code, for any reason?

Only as a last resort.

You should never monkey-patch your own classes. There's simply no point. You control them, you can make them do what you want in the first place.

You should never monkey-patch classes in a library. (The exception to this rule are libraries whose sole purpose it is to monkey-patch something, e.g. Marc-André Lafortune's backports library, which monkey-patches Ruby 1.8.6, 1.8.7, 1.9.0 and 1.9.1 with as much as possible of the functionality from Ruby 1.9.2.) You may provide an add-on library which provides monkey-patches that make it easier to use your library (e.g. you have an encryption library which provides a Kryptonite.encrypt(str) method and you provide an add-on String#encrypt method), but that add-on should be in a separate library, which the user needs to explicitly require. It should be fully optional.

You should not monkey-patch core classes. This refers to classes like Array or Symbol in Ruby, but for a Rails library, I would also include classes like ActiveRecord::Base under the "core" label. (Same caveat as above. E.g. in versions of Rails before 3.0, there was no well-defined plugin API, monkey-patching was the only way to extend Rails. Without people who broke this rule, there would never haven been any plugins, and Rails would never be where it is now.)

Try inheritance first. Try composition (wrappers, proxies, facades, adapters, …) first. Try refactoring first. Try helper objects first. Only if that doesn't work, turn to monkey-patching.

Be respectful when you monkey-patch: if you are adding a new method, make sure it doesn't already exist, and deal with it if it does (e.g. call it from your method). If you are wrapping an existing method, make sure that if somebody else has already wrapped it, their wrapper gets called and that when somebody wants to wrap it afterwards, your wrapper allows that. (In particular, this means you must preserve the method's existing contract.)

Put your monkey-patches in a mixin, if at all possible. That way, they show up in the inheritance chain, which will give anybody who tries to debug the code a fighting chance to figure out what is going on. Put your monkey-patches in separate, obviously named files.

Finally, how is this sort of thing handled in a large-scale, production coding environment? In other words, do people in the programming industry actually do this in code that others will use? Or even if they don't, how do you ensure that some plugin author somewhere isn't doing something like this that will ruin an essential part of your program?

Don't work with "really bad programmers", as you call them.

As simplistic as this sounds, that's basically what it boils down to. Yes, of course, you can write tests, do code reviews, practice pair programming, use static analysis tools, run your code with warnings enabled (e.g. the code you posted in your question will generate a warning: method redefined; discarding old ==). But for me, that's all something that a not-really-bad-programmer would do anyway.

寄居者 2024-10-09 03:23:12
  1. 在某些情况下是的。如果您遵循一个类负责一项工作且仅负责一项工作的范例,那么重新开放类的使用通常会(尽管不一定)破坏封装。然而这似乎并不是 Ruby 中的传统。例如,Array 类充当列表、数组和堆栈,因此 stdlib 似乎也不遵守严格的封装。我想是品味问题。
  2. 我不知道有什么办法。也许其他人会想出一些办法。
  3. 我的观点是,如果您正在编写一个其他人会使用的库,我会避免这样做。如果您正在编写一个应用程序并且有需求(简单的例子:您需要一个用于数字数组的平均方法 - 这是在增加可读性和不进行猴子修补之间进行选择),我会选择它。
  4. 现实世界中最著名的猴子补丁程序是 Rails。因此,通常最好记录对核心类的特别好的更改。是的,测试有帮助。
  1. In some cases yes. If you follow the paradigm of one class is responsible for one job and one job only then uses of reopening classes will often (though not necessarily) break encapsulation. It seems that this however not the tradition in ruby. For example the Array class acts as a list, array and a stack so the stdlib doesn't seem to adhere to strict encapsulation either. Matter of taste I guess.
  2. I don't know of any way. Maybe someone else will come up with something.
  3. My opinion is that I'd avoid doing it if you're writing a library that others will use. If you're writing an application and the need comes (trivial example: you need to have a mean method for arrays of numbers - it's a choice between added readability and not monkeypatching) I'd go for it.
  4. The most (in)famous real world monkeypatcher are rails. So often it's good to document especially well changes to core classes. And yes testing helps.
你怎么这么可爱啊 2024-10-09 03:23:12

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

封装是为了隐藏实现细节,而不是规定如何使用类。在 ruby​​ 中,您通常会尊重私有变量,并且当您想要解决这个问题时,您知道您正在做的事情是一种黑客行为,在升级库时可能会破坏。我想说,大约 90% 的情况下,我会在测试情况下破坏封装,而当我在其他语言中无法做到这一点时,我发现这非常令人恼火

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

这有点违反了整个“公开课”的原则,不是吗;-)

第三,我是否应该“开放”
我的代码中的类,出于什么原因?

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

最后,这是怎么回事?
大规模生产、处理
编码环境?换句话说,做
编程行业的人
实际上是用其他人的代码来做到这一点
会用吗?或者即使他们不这样做,又如何
你能确保某个插件作者
某处没有做类似的事情
这会毁掉一个重要的部分
你的程序?

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

话虽这么说,在 ruby​​ 中这种情况经常发生,在 Rails 中这就是获取插件的方式。

我正在开发一个具有 250k loc 代码库的产品,并且我们已经修补了很多东西。我们还实践 TDD(并且 loc 与测试 loc 的比例为 1:1.5),并在提交到主线存储库之前运行所有测试。所有猴子补丁都位于文件中,其用途在“配置/初始化程序”中明确标记,并且所有补丁都经过全面测试。已经在那里工作一年了,至少在那段时间我们从未遇到过与猴子补丁相关的问题。

话虽如此,这是我加入过的最好的团队,我们都非常致力于极限编程。如果不是这种情况,我认为 Rails 不是一个好主意。您需要相信您的团队能够使用像 ruby​​ 这样强大的语言做正确的事情,并拥有尽可能多的制衡。

First, does this, in fact, violate the
OO principle of encapsulation?

Encapsulation is there to hide implementation details away, not dictate how a class should be used. In ruby, you typically respect private variables, and when you want to get around that you know what you are doing is a hack that may break when you upgrade the library. I would say ~90% of the time that I will break encapsulation is in testing situations, and I find it very irritating when I can't do that in other languages

Second, is there a way, as a
programmer, that I can guarantee in my
code that I am working with an
unmodified version of a class?

That would kind of violate the whole "open class" thing, wouldn't it ;-)

Third, should I ever be "opening"
classes in my code, for any reason?

Think of it as a "last resort" type of thing. Usually the answer would be "no", since you control the class definition there shouldn't be a need to. Adding stuff to the singleton class of specific instances is a totally other story though ;-)

Finally, how is this sort of thing
handled in a large-scale, production
coding environment? In other words, do
people in the programming industry
actually do this in code that others
will use? Or even if they don't, how
do you ensure that some plugin author
somewhere isn't doing something like
this that will ruin an essential part
of your program?

As a rule, if a library is opening up another library it should be done as a last resort (i.e. you can't accomplish the same thing through normal OO features), and when they do it they should make damn sure it hasn't already been opened by someone else. There are tricks you can do to make the process safer, like the old alias_method_chain, or the new stuff around using mixins and calling super.

That being said, in ruby that happens all the time, in rails it is how you get plugins.

I work on a product with a 250k loc codebase, and we have monkey patched loads of things. We also practice TDD (and have a 1:1.5 ratio in loc to test loc), and run all tests before committing to the mainline repository. All monkey patches are in files with their purpose clearly labelled in "config/initializers", and all of them are fully tested. Been working there for a year now, and at least in that time we have never run across a monkey-patch related issue.

That being said, it is the best team I have ever been on, and we are all extremely committed to extreme programming. If either of those were not the case, I don't think rails would be a good idea. You need to trust your team to do the right thing in a language with as much power as ruby, and have as many checks and balances as you can.

醉南桥 2024-10-09 03:23:12
  1. 简短回答:是的。更长的答案:有点。封装的目的确实是为了防止这种事情发生,但是在其他语言中可能会违反封装,尽管通过更困难的方式。

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

  3. 我不确定你这个问题的意思。

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

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

  1. Short answer: Yes. Longer answer: kind of. The point of encapsulation is indeed to prevent this sort of thing from happening, however encapsulation can be violated in other languages, albeit via more difficult means.

  2. Test cases, perhaps, but again, Ruby is notorious for having quirks when writing applications, especially when using heavy frameworks like Rails, which was guilty for polluting the global namespace and causing strange results on unexpected occasions until version 3 came out.

  3. I'm not sure what you mean by this question.

  4. In the real world, developers decide which packages to use, preferably heavily-tested ones.

As an extra note, other developers can and frequently do break programs they use. Encapsulation isn't a software feature that locks out access to parts of your application, it's a language feature that helps prevent coders from directly messing up your code.

英雄似剑 2024-10-09 03:23:12

我目前的 Ruby 经验告诉我:

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

My current experience of Ruby tells me that:

  • Yes, since you can add a method to return private attributes of external classes: programs can break encapsulation at will.
  • No, there is nothing you can do to prevent that, it's a language feature.
  • Yes, sometimes it can seem useful or at least produce good looking code to add methods to existing classes: for example adding applicative filtering methods to String or Array. In any case, create these methods in modules and include those. I particularly like the way it's done in ActiveRecord, read their sources it's all nice and clean.
  • In large scale code, unless you have good unit tests and disciplined developers, consider switching to a less brittle language (yes I know some of you will disagree).
单身情人 2024-10-09 03:23:12

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

话又说回来,您可能会使用其他人没有使用过的插件组合。

For part 4., there's the principle of "Select isn't broken". If lots of people are using the plugin you're using, odds are if a plugin does something bad, then someone would have spotted it.

Then again, you could be using a combination of plugins no-one else does.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文