— Feb 23, 2016
A quick post about when I choose to alias methods in Ruby, and when I don’t.
If you have programmed Ruby at all, you have probably noticed that there are a lot of ways to do one thing.
For example, to raise an error, you can use the raise
method or you can use the fail
method.
Both have exactly the same behavior.
In fact, they are exactly the same thing.
If you check out the docs for fail
, you’ll see… raise
.
So why have it?
Semantics.
Ruby provides us with the ability to choose the method that best communicates intent.
The late Jim Weirich had a somewhat well known semantic for fail
vs. raise
.
He would use fail
for irrecoverable errors (or… failures), and he would use raise
for errors that may be caught elsewhere.
Avdi Grimm was kind enough to capture Jim’s opinion in a post.
There are a number of other examples throughout the Ruby codebase, e.g. Enumerable#inject and Enumerable#reduce.
It’s all in the name of writing self-evident code.
In short, alias a method when the new method you’re exposing is literally another word for the same thing. Let’s check out an example.
Here’s a snippet of code that was recently discussed in code review.
class Foo
def part
locate
end
private
def locate
# some part locating logic
end
end
The comment came on the implementation of part
.
Why not just
alias_method :part, :locate
?
It makes sense, the entire implementation of part
is literally calling another method.
So save a couple lines and make it an alias, right?
This introduces a cognitive “WAT”.
Making the part
method an alias of locate
states that it literally carries the same semantic meaning as locate
.
These two methods actually have two distinct purposes.
part
- expose a public interface on Foo
for getting its part
.locate
- do something to find the part
for Foo
.This becomes especially apparent when a new requirement comes in.
Optimize Foo#part by memoizing the located value.
If you had aliased locate
, you would now be ripping that out to implement an actual part
method.
def part
- locate
+ @part ||= locate
end
Beyond this practical advantage, there is also cognitive gain. You know at a glance of the class that “Foo has a part” rather than “Apparently Foo’s locate is also known as a part” (???).
Assume that you have shipped Foo
.
It’s out in the wild and a number of other’s depend on the fact that Foo
has a part
.
However, you decide that part
isn’t the best name for this data member.
You want to refer to it as widget
henceforth.
def part
@part ||= locate
end
+alias_method :widget, :part
The reader of your code immediately recognizes that widget
is just another name for part
.
But please do consider a deprecation message.