Ruby 的“公开课”吗?打破封装?
在 Ruby 中,程序员可以更改预定义的类。因此,一个非常糟糕的程序员可能会做这样的事情:
class String
def ==(other)
return true
end
end
显然,几乎没有人会这么愚蠢,但是对预定义类进行更细微的更改可能会导致已经工作的代码出现问题,在我看来,这种想法违反了封装原则。
四个问题:
- 第一,这实际上是否违反了面向对象的封装原则?
- 其次,作为一名程序员,有没有一种方法可以保证 我正在使用类的未修改版本的代码?
- 第三,我是否应该在我的代码中为任何“开放”类 原因?
- 最后,在大规模的生产编码环境中如何处理这种事情?换句话说,编程行业的人们是否真的用其他人的代码来做到这一点? 会用吗?或者即使他们不这样做,你如何确保一些人 某个地方的插件作者没有做这样的事情 会毁掉你程序的重要部分吗?
我知道这是一个有点主观的问题,但我真的很想知道更广泛的编程社区对这种所谓的“猴子补丁”有何看法。
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:
- First, does this, in fact, violate the OO principle of encapsulation?
- 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? - Third, should I ever be "opening" classes in my code, for any
reason? - 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
是的。
还没有。 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 中的
Array
或Symbol
之类的类,但对于 Rails 库,我还会在“核心”标签。 (与上面相同的警告。例如,在 3.0 之前的 Rails 版本中,没有明确定义的插件 API,猴子补丁是扩展 Rails 的唯一方法。如果没有人违反此规则,就会有从来没有任何插件,Rails 也永远不会达到现在的水平。)首先尝试继承。首先尝试组合(包装器、代理、外观、适配器……)。首先尝试重构。首先尝试辅助对象。 仅如果这不起作用,请转向猴子修补。
当你进行猴子补丁时要尊重:如果你要添加一个新方法,请确保它尚不存在,如果存在则对其进行处理(例如从你的方法中调用它)。如果您要包装现有方法,请确保如果其他人已经包装了它,则他们的包装器会被调用,并且当有人想要随后包装它时,您的包装器会允许这样做。 (特别是,这意味着您必须保留该方法的现有契约。)
如果可能的话,将您的猴子补丁放入 mixin 中。这样,它们就会出现在继承链中,这将为任何尝试调试代码的人提供一个弄清楚发生了什么的机会。将你的猴子补丁放在单独的、明显命名的文件中。
不要和你所说的“非常糟糕的程序员”一起工作。
尽管这听起来很简单,但基本上就是这样。是的,当然,您可以编写测试,进行代码审查,练习结对编程,使用静态分析工具,在启用警告的情况下运行代码(例如,您在问题中发布的代码将生成一个警告:方法重新定义;丢弃旧==)。但对我来说,这都是一个不太糟糕的程序员会做的事情。
Yes.
Not yet. Classboxes in Ruby 2.0 are (hopefully) going to be the solution.
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 aKryptonite.encrypt(str)
method and you provide an add-onString#encrypt
method), but that add-on should be in a separate library, which the user needs to explicitlyrequire
. It should be fully optional.You should not monkey-patch core classes. This refers to classes like
Array
orSymbol
in Ruby, but for a Rails library, I would also include classes likeActiveRecord::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.
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.封装是为了隐藏实现细节,而不是规定如何使用类。在 ruby 中,您通常会尊重私有变量,并且当您想要解决这个问题时,您知道您正在做的事情是一种黑客行为,在升级库时可能会破坏。我想说,大约 90% 的情况下,我会在测试情况下破坏封装,而当我在其他语言中无法做到这一点时,我发现这非常令人恼火
这有点违反了整个“公开课”的原则,不是吗;-)
将其视为“最后的手段”类型的事情。通常答案是“否”,因为您控制类定义,所以不需要这样做。不过,向特定实例的单例类添加内容完全是另一回事;-)
一般来说,如果一个库正在开放另一个库,那么它应该作为最后的手段来完成(即你无法通过正常的面向对象功能完成同样的事情),并且当他们这样做时,他们应该确保它没有这样做已经被别人打开了。您可以采取一些技巧来使过程更安全,例如旧的alias_method_chain,或者使用mixins和调用super的新东西。
话虽这么说,在 ruby 中这种情况经常发生,在 Rails 中这就是获取插件的方式。
我正在开发一个具有 250k loc 代码库的产品,并且我们已经修补了很多东西。我们还实践 TDD(并且 loc 与测试 loc 的比例为 1:1.5),并在提交到主线存储库之前运行所有测试。所有猴子补丁都位于文件中,其用途在“配置/初始化程序”中明确标记,并且所有补丁都经过全面测试。已经在那里工作一年了,至少在那段时间我们从未遇到过与猴子补丁相关的问题。
话虽如此,这是我加入过的最好的团队,我们都非常致力于极限编程。如果不是这种情况,我认为 Rails 不是一个好主意。您需要相信您的团队能够使用像 ruby 这样强大的语言做正确的事情,并拥有尽可能多的制衡。
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
That would kind of violate the whole "open class" thing, wouldn't it ;-)
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 ;-)
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.
简短回答:是的。更长的答案:有点。封装的目的确实是为了防止这种事情发生,但是在其他语言中可能会违反封装,尽管通过更困难的方式。
也许是测试用例,但是,Ruby 因在编写应用程序时出现怪癖而臭名昭著,尤其是在使用像 Rails 这样的重型框架时,Rails 因污染全局命名空间并在意外情况下导致奇怪的结果而感到内疚,直到版本 3 发布。
我不确定你这个问题的意思。
在现实世界中,开发人员决定使用哪些包,最好是经过严格测试的包。
作为额外说明,其他开发人员可以并且经常破坏他们使用的程序。封装不是一种锁定对应用程序部分内容的访问的软件功能,它是一种语言功能,有助于防止编码人员直接弄乱您的代码。
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.
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.
I'm not sure what you mean by this question.
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.
我目前的 Ruby 经验告诉我:
My current experience of Ruby tells me that:
对于第 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.