Kernel#send Should Be Removed
Opinion: Kernel#send
should be deprecated and removed from future rubies.
send
always felt weird when I was an active ruby developer but I have never seen any strong criticisms on it. It's either an attack on all family of send-like calls or a fierce defence that "using send
is not a bad practice". Only some style guides argue against send
because it may overlap with existing methods. This post was written after reading one of defences.
send
belongs to a family of 3 methods that allow calling a method by its name as a parameter:
BasicObject#__send__
, the base methodKernel#send
, alias forBasicObject#__send__
Kernel#public_send
, that calls only public methods
My problem is only with the second one.
What's wrong with send
?
"What is the problem? After all it's only an alias!". Two arguments combined:
It breaks encapsulation
It looks perfectly normal
Breaking encapsulation is a too common practice in a Ruby world. Monkey patch a library? Why not! bundle update
now breaks the project? It always does that! Let's invent updating practices instead of not interfering with guts of external libraries.
Non-public methods are not in any library's interface and are never covered by semver promise for example so you work with them at your own risk. If you do it regularly, it's a code smell.
class DemoObject def update_date(value) # ... end private def update_caches(keys) # ... end end obj = DemoObject.new field = 'caches' # ...tons of code...
Let's update a field dynamically:
# clearly not what you might want obj.send :"update_#{field}", new_value # error here, at least it won't break something in an unexpected way obj.public_send :"update_#{field}", new_value
But look again at these calls. The first one looks more natural, more logical and it is simply shorter. This technically makes this metaprogramming pattern unsafe by default. Maybe if they were named private_send
and send
, this post wouldn't exist.
Ok, maybe the library is bugged and there is some error with caches
# this looks like a regular call # you may easily miss it: obj.send :update_caches, [:key1, :key2] # this looks like a code smell # so if there is no huge explanation # why it is done and when it can be removed, # you do `git blame` and go hit someone with a hammer: obj.__send__ :update_caches, [:key1, :key2]
The second method clearly shows us that you're messing with some internals and you must have a good reason for that. This weirdly looking call is easy to spot even if you're looking through code without proper attention.
public_send
and __send__
public_send
-
public_send
is fine as long as you put only trusted data in the first parameter. It's a very useful and safe tool for metaprogramming. It's one of that parts that I love about the language. __send__
-
First,
__send__
is a base feature of the language, a building block that cannot be easily removed or hidden. Second, as stated above, the method name with double underscores on both sides clearly shows that something hacky is happening and indicates a potential code smell.
Comments
Comments powered by Disqus