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 method

  • Kernel#send, alias for BasicObject#__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