In Defense of Alias
CommentsAs some of you already know, I’ve recently started a new job as Director of Engineering at nVisium.
One of the first few things nVisium requested of me was to develop a coding style guide, so our code would read more consistently. Naturally, I used the community-driven guide maintained by Bozhidar Batsov (author of RuboCop) as a starting point, but ended up making my own tweaks (style is subjective, after all!).
Thanks to the magic of git diff
I now have a record of styles I feel have
gotten an unnecessarily bad rap, and I want to talk about one of them today:
I prefer alias over alias_method.
Arguments Against alias
The arguments I’ve seen against using alias
instead of alias_method
are:
- It’s a keyword, not a method, so it doesn’t need a comma between its arguments, which can be barewords. This can throw some people off.
alias_method
is a method onModule
, so it can be redefined if you like.alias
is lexically scoped. This means thatself
for purposes of the lookup of the method being aliased is whateverself
is where it’s encountered in the source code, not whateverself
is at runtime.
How I Use alias
When aliasing methods, especially in non-library code, I’m doing so at the class level. Something like:
class MyContainer
# ...
def store(key, value)
@values[key] = value
end
alias :[]= :store
end
In this context, it absolutely makes sense that alias
is a keyword. It’s just
like def
in terms of its results (a method), but differs in the way that it
determines what code is executed when the method is dispatched. Note that I use
symbols instead of barewords so that my syntax highlighting looks nicer.
Symbols totally work. You don’t have to use barewords!
As far as the argument that alias_method
can be redefined: Don’t redefine
methods like alias_method
, and especially not on core classes like Module
.
If you really need some kind of alternate alias behavior, define your own in a
module, and include or extend it as needed. It’s totally cool if you want to
use alias_method
inside your own method.
The argument that obviously carries the most weight is the one about lexical scope, so let’s take a closer look at that one.
The Lexical Argument, In Depth
The argument against lexically-scoped aliases (and in favor of alias_method
)
is as follows:
Say you have some classes like this:
class Person
def full_name
'Bob Bobbenheimer'
end
alias :name :full_name
end
class Developer < Person
def full_name
"#{super}, Software Developer"
end
end
If you instantiate Developer
, you might think that Developer#name
would
return “Bob Bobbenheimer, Software Developer”, but it won’t. This is because
the alias defined in Person
refers specifically to the full_name
method as
it existed at the location in the source at which the alias
keyword was used.
Now, if it were the case that we could rewrite Person
as…
class Person
def full_name
'Bob Bobbenheimer'
end
alias_method :name, :full_name
end
…and the code would behave as we’d assume, then I would agree: we should
always use alias_method
. But, of course, that isn’t the case. A call to
alias_method
inside a class body will be executed as the class is loaded, so
once again, self
refers to the same method that alias
would: the full_name
method as it was defined in Person
.
Hmm, maybe if you are doing some cutesy metaprogramming-type stuff like:
class Person
def self.inherited(subclass)
subclass.class_eval { alias_method :name, :full_name }
end
def full_name
'Bob Bobbenheimer'
end
end
Nope, in that case, the inherited hook gets called before Developer
would have
defined its own full_name
method, so the alias will still point to
Person#full_name
. The specific case in which the differences would manifest
is:
class Person
def self.add_name_alias
alias_method :name, :full_name
end
def full_name
'Bob Bobbenheimer'
end
add_name_alias
end
class Developer < Person
def full_name
"#{super}, Software Developer"
end
add_name_alias
end
In this case, using alias
in the Person.add_name_alias
method would still
resolve to Person#full_name
, while alias_method
will resolve to
Developer#full_name
, because its notion of self
is determined at runtime.
This example is obviously contrived for discussion’s sake, but the more generally-applicable thing to note here is that this issue will only bite you if you are aliasing things at runtime. In real-world scenarios, this probably means you are monkey patching something or writing some clever class macros.
On Brain Compatibility
And this is why I default to using alias
, and not alias_method
. It states
something to the reader.
Dear reader of this code, be you future me or someone I haven’t met yet, I guarantee to you that the method to which I’m pointing is discernable by simply reading the source code you see here, as humans generally do.
—alias
Contrast this with:
Dear reader of this code, be you future me or someone I haven’t met yet: I could be pointing at anything. Hope your test suite’s great. YOLO!
—alias_method
Because here’s the thing: when we read code, our brain naturally processes code (and most written communication, for that matter) in lexical scope. We look to the context around our code for clues as to how to interpret it.
There are more Rubyish ways to indicate to the reader that a method should be looked up at runtime. Here’s my favorite:
class Person
def full_name
'Bob Bobbenheimer'
end
def name
full_name
end
end
class Developer < Person
def full_name
"#{super}, Software Developer"
end
end
Look at that! No class macros. No aliasing. No metaprogramming. It Just Works™.
You exclaim, “but now you’re dispatching twice! Your ROFLscales will suffer!”
Fair enough. I’ll cross that bridge when I come to it.
When Should I Resort to alias_method?
So, with that out of the way, I still sometimes use alias_method
when I need
it. The following legitimate use cases come to mind:
- I want to alias to a method being determined by a variable name. Even without
scoping issues,
alias
’s support for barewords make using a variable name impossible. - I am intentionally looking up a runtime binding for an alias. This means I am aliasing from within another method. This means I am probably doing some cutesy metaprogramming trick. This means I should consider the life choices that have led me to this point, and determine if there’s a clearer way to accomplish what I want to do.
By defaulting to alias
and only resorting to alias_method
when it’s
necessary, I’m intentionally creating one of those helpful points for
reflection on why I’m doing what I’m doing. Often, a better application design
is the result.