Steve Klabnik recently said in a pull request for a utility module:
[The code] obscures the fact that these are class methods, and we want to use them that way. Plus, I think that
extend self
is generally an anti-pattern, and shouldn't really be used except in some cases. I thought about it, and I think this is one of those cases.
extend self
idiom ever appropriate?It's not exactly helpful that he doesn't say 1) why he thinks it is an anti-pattern (other than obscuring the fact that you are defining class methods) or 2) why, having thought about it, this is one of the cases he thinks it shouldn't be used, so it's hard to specifically counter any of his arguments.
However, I'm not convinced that extend self
is an anti-pattern. A utility module seems like a good example of a use case for it. I've also used it as easy storage for test fixtures.
I think it's worth examining what extend self
is, what possible problems there might be with it, and what alternatives there are.
At it's core, it is simply a way to avoid having to write self.
before every method definition in a module that you never intend to mix into a class, and that will therefore never have an 'instance' of itself created, so can by definition only have 'class' methods (if you want to be able to call them, that is).
Does it disguise the fact that you intend the methods to be used as class methods? Well, yes, if you don't look at the top of the file to where it says extend self
, that's possible. However, I would argue that if it's possible for you to make this confusion, your class is probably too complicated anyway.
It should be obvious from your class - from its name and from its contents - that it is intended as a collection of utility functions. Ideally it wouldn't be much more than a screen tall anyway, so extend self
would almost never be out of sight. And as we'll see, the alternatives also suffer almost exactly the same problem.
One alternative would be to use class << self
like this:
module Utility
class << self
def utility_function1
end
def utility_function2
end
end
end
I am not a fan of this, not least because it introduces an extra layer of indentation. It's also ugly (totally subjective, I know). It also suffers from exactly the same problem of 'obscuring' the fact that you're defining class methods.
You're also free, using this approach, to define instance methods outside the class << self
block - which might lead to the temptation to do so (although I'd hope it wouldn't), so I'd argue that extend self
is superior in this regard by removing the possibility of this muddying of the waters.
(The same is true, of course, of the 'long-hand' style of using def self.utility_function
.)
Another approach could be to use a singleton object. I don't think this is a good idea at all, because a singleton object is an object for a reason - it's meant to hold state and do stuff, but also be the only one in existence. That simply doesn't make sense for a utility module, which should be a series of independent stateless functions. You don't want MathUtils.cos(90)
to ever return a different value based on internal state of MathUtils
, right? (I know that you can of course hold state in a module and do all these things, but it's more of a semantic division for me than a technical one).
It also leads to the same problem of arguably obscuring the fact that the methods are intended to be called as class methods (sort of). They are defined as instance methods, and you call them as instance methods, but by first getting the single instance of the class by calling the class method instance
.
class MathSingleton
include Singleton
def cos x
end
end
MathSingleton.instance.cos x
This would be a terrible alternative to extend self
for this purpose. But look, also, the only thing indicating that these methods are to be used as methods on the singleton instance is that one line just up at the top, just like extend self
.
So what other possible downsides are there? I don't know of any, but I'd be interested to hear them if anyone else does.
I would argue that extend self
leads to shorter code, that leaves out the extraneous self.
s and allows you to concentrate on the names and therefore meanings of its methods.
It also has the nice property that, if you are writing another class that uses lots of your utility functions, for example, you can just mix it in and they will be available without having to use the module name each time. Much like static imports work in other languages.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With