修补 Ruby 的基类(例如 Fixnum)是否可以接受?
我对 Ruby 还很陌生(阅读 Pickaxe 并将大部分时间花在 irb 上),现在我知道可以在 Ruby 中修补类,我想知道什么时候可以接受这样做,特别是是否可以接受修补 Ruby 的基类。 例如:我在此处回答了另一个 Ruby 问题,其中发帖者想知道如何从 DateTime
中减去小时。 由于 DateTime
类似乎没有提供此功能,因此我发布了一个答案,将修补 DateTime
和 Fixnum
类作为可能的解决方案。 这是我提交的代码:
require 'date'
# A placeholder class for holding a set number of hours.
# Used so we can know when to change the behavior
# of DateTime#-() by recognizing when hours are explicitly passed in.
class Hours
attr_reader :value
def initialize(value)
@value = value
end
end
# Patch the #-() method to handle subtracting hours
# in addition to what it normally does
class DateTime
alias old_subtract -
def -(x)
case x
when Hours; return DateTime.new(year, month, day, hour-x.value, min, sec)
else; return self.old_subtract(x)
end
end
end
# Add an #hours attribute to Fixnum that returns an Hours object.
# This is for syntactic sugar, allowing you to write "someDate - 4.hours" for example
class Fixnum
def hours
Hours.new(self)
end
end
我修补了这些类,因为我认为在这种情况下,它将产生一个清晰、简洁的语法,用于从 DateTime
中减去固定的小时数。 具体来说,您可以根据上面的代码执行类似的操作:
five_hours_ago = DateTime.now - 5.hours
这看起来相当漂亮且易于理解; 但是,我不确定弄乱 DateTime
的 -
运算符的功能是否是一个好主意。
对于这种情况,我能想到的唯一替代方案是:
1。 只需即时创建一个新的 DateTime
对象,在调用 new
时计算新的小时值
new_date = DateTime.new(old_date.year, old_date.year, old_date.month, old_date.year.day, old_date.hour - hours_to_subtract, date.min, date.sec)
2. 编写一个实用程序方法,该方法接受 DateTime
以及要从中减去的小时数
基本上,只是方法 (1) 的包装:
def subtract_hours(date, hours)
return DateTime.new(date.year, date.month, date.day, date.hour - hours, date.min, date.sec)
end
3. 向 DateTime
添加新方法,而不是更改 #-()
的现有行为
也许是一个新的 DateTime#less
方法可以与 Fixnum#hours 补丁一起使用,以允许这样的语法:
date.less(5.hours)
但是,正如我已经提到的,我采用了修补方法,因为我认为它会产生更具表现力的语法。
我的方法有什么问题吗,或者我应该使用三种替代方案之一(或我没有想到的另一种)来做到这一点? 我有一种感觉,修补正在成为我解决 Ruby 问题的新“锤子”,所以我想获得一些关于我是否以“Ruby 方式”做事的反馈。
I am still very new to Ruby (reading through the Pickaxe and spending most of my time in irb
), and now that I know it's possible to patch classes in Ruby, I'm wondering when it's acceptable to do so, specifically whether it's acceptable to patch Ruby's base classes. For example: I answered another Ruby question here where the poster wanted to know how to subtract hours from a DateTime
. Since the DateTime
class doesn't seem to provide this functionality, I posted an answer that patches the DateTime
and Fixnum
classes as a possible solution. This is the code I submitted:
require 'date'
# A placeholder class for holding a set number of hours.
# Used so we can know when to change the behavior
# of DateTime#-() by recognizing when hours are explicitly passed in.
class Hours
attr_reader :value
def initialize(value)
@value = value
end
end
# Patch the #-() method to handle subtracting hours
# in addition to what it normally does
class DateTime
alias old_subtract -
def -(x)
case x
when Hours; return DateTime.new(year, month, day, hour-x.value, min, sec)
else; return self.old_subtract(x)
end
end
end
# Add an #hours attribute to Fixnum that returns an Hours object.
# This is for syntactic sugar, allowing you to write "someDate - 4.hours" for example
class Fixnum
def hours
Hours.new(self)
end
end
I patched the classes because I thought in this instance it would result in a clear, concise syntax for subtracting a fixed number of hours from a DateTime
. Specifically, you could do something like this as a result of the above code:
five_hours_ago = DateTime.now - 5.hours
Which seems to be fairly nice to look at and easy to understand; however, I'm not sure whether it's a good idea to be messing with the functionality of DateTime
's -
operator.
The only alternatives that I can think of for this situation would be:
1. Simply create a new DateTime
object on-the-fly, computing the new hour value in the call to new
new_date = DateTime.new(old_date.year, old_date.year, old_date.month, old_date.year.day, old_date.hour - hours_to_subtract, date.min, date.sec)
2. Write a utility method that accepts a DateTime
and the number of hours to subtract from it
Basically, just a wrapper around method (1):
def subtract_hours(date, hours)
return DateTime.new(date.year, date.month, date.day, date.hour - hours, date.min, date.sec)
end
3. Add a new method to DateTime
instead of changing the existing behavior of #-()
Perhaps a new DateTime#less
method that could work together with the Fixnum#hours
patch, to allow syntax like this:
date.less(5.hours)
However, as I already mentioned, I took the patching approach because I thought it resulted in a much more expressive syntax.
Is there anything wrong with my approach, or should I be using one of the 3 alternatives (or another one I haven't thought of) in order to do this? I have the feeling that patching is becoming my new 'hammer' for problems in Ruby, so I'd like to get some feedback on whether I'm doing things the "Ruby way" or not.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我个人的回答,简而言之:核心级补丁锤应该位于工具箱底部。 还有很多其他技术可供您使用,并且几乎在所有情况下它们都足够、更干净且更可持续。
不过,这实际上取决于您编码的环境。 如果这是一个个人项目 - 当然,可以根据自己的喜好进行修补! 当您与一大群程序员一起长期处理大型代码库时,问题就开始出现。 我工作的组织拥有超过 100KLOC 的 Ruby 代码库和大约 20 名开发人员,我们已经开始严厉打击猴子修补,因为我们已经看到它会导致令人头疼的浪费工时的行为太频繁了。 此时,我们几乎只能容忍它临时修补尚未合并或不会合并我们的源补丁的第三方代码。
My personal answer, in a nutshell: the core-class patching hammer should be at the bottom of your toolbox. There are a lot of other techniques available to you, and in almost all cases they are sufficient, cleaner, and more sustainable.
It really depends on the environment in which you are coding, though. If it's a personal project - sure, patch to your heart's content! The problems begin to arise when you are working on a large codebase over a long period of time with a large group of programmers. In the organization I work for, which has Ruby codebases of over 100KLOC and and twenty or so developers, we have started to crack down pretty hard on monkey patching, because we've seen it lead to head-scratching, man-hour wasting behavior far too often. At this point we pretty much only tolerate it for temporarily patching third-party code which either hasn't yet incorporated or won't incorporate our source patches.
就我个人而言,我认为向基类添加方法是可以接受的,但修改现有方法的实现是不可接受的。
Personally, I think it's acceptable to add methods to the base classes, but unacceptable to modify the implementation of existing methods.
最安全的方法是定义您自己的类,该类继承自内置类,然后将您的新内容添加到您的新类中。
但显然现在只有声明新类的对象才能获得新行为。
The safest way is to define your own class that inherits from the built-in one, then add your new stuff to your new class.
But obviously now you only get the new behavior if you declare objects of your new class.
我认为它是这样的:如果你真的认为大多数其他程序员会同意你的补丁,那么很好。 如果没有,也许您应该实现一个代码库?
I think its like this: If you honestly feel that most other programmers would agree with your patches, then fine. If not, perhaps you should instead be implementing a code library?