Objects that lie
In many programming languages — but especially in Ruby — objects can lie. These fibs are usually harmless — even useful — but they can mess things up if you don’t expect them.
Take this code for example.
def perform(message)
if message == "hello"
# do something
end
end
Our perform
method here receives a message
and does something if the message is equal to "hello"
.
Or does it?
In fact, it does the thing if the message
object claims to be equal to "hello"
. But objects can lie. For all we know, the ==
method on the message
object could be this.
def ==(other)
true
end
I prefer to flip the operands around in these situations.
def perform(message)
if "hello" == message
# do something
end
end
This code doesn’t flow quite as well if you try to read it like English. But we’re not really writing English. We’re writing Ruby, which is much better. Especially when it comes to expressing precise algorithms.1
This code now only ever sends the ==
method to an instance of String
since we’re calling it directly on a string literal.
In fact, if we have frozen string literals enabled, we can be sure this method is only ever sent to the same exact string object every time it’s called.
We’ve subtly reduced the complexity of the method, localised knowledge and likely made it faster in the process.2 Looking at just this small snippet of code, we can now determine what will happen for a given input with a high degree of accuracy.
I tend to prefer sending methods to literals, constants or objects I own when possible, in that order of priority.
But sometimes it’s not possible. Sometimes you need to send the method to a parameter and the risk is quite high that some user may have overridden the method you need to call.
Let’s say we need to look up the class of an object. That’s easy, right? Just ask it by calling the class
method. But it’s possible that someone has overridden the class
method, and that will break our code.
There’s a trick you can use in Ruby to mitigate this risk. First, let’s figure out where the class
method usually comes from.
In an IRB console, you can run object.method(:class).owner
on an object that hasn’t overridden the class
method. It tells us the owner of this method is Kernel
.
So let’s grab that instance method and store it in a constant for later.
ORIGINAL_CLASS_METHOD = Kernel.instance_method(:class)
This gives us an UnboundMethod
object, which is essentially a method that doesn’t have a self
yet. Now, instead of calling object.class
, we can use the method we grabbed from Kernel
.
ORIGINAL_CLASS_METHOD.bind_call(object)
bind_call
on an UnboundMethod
calls the method but with self
bound to the first argument. All subsequent arguments are passed along.
It’s much less likely that someone would have overridden the class
method on Kernel
than on their own class.
It’s up to you when if ever it makes sense to take these precautions, but I at least encourage you to think carefully about which object is receiving the methods you send, especially when it comes to operators.