Ruby Paper Cuts

I love programming in Ruby and (especially) the Ruby on Rails framework. That said, they both have some oddities and conventions that leave me scratching my head, making me wonder what such smart people are thinking, and questioning the very nature of reality…you know, much like an episode of a Disney+ or CW show.

Most of the following are personal preferences / stylistic choices, but I do think they have an appreciable impact on various aspects of clean coding practices (esp. readability) and can lead to unnecessary bugs, which is why I feel they count as actual paper cuts vs. simple annoyances.

Also, let me preface the following with: At times in this post, you may notice a slightly sarcastic tone and perhaps the use of more exclamation points than usual for my writing. This is simply due to the fact that I do find myself baffled by some of these choices and I can’t help a bit of that slipping out. Please know that this is all in good humor and I respect that everyone has a right to their own opinion.

So please take the following with a grain of salt while also noting that I use Ruby, Rails, and Ruby on Rails interchangeably below.

Papercut #1: Look Ma, No Parenthesis

Why oh why do Ruby programmers think it’s a good idea to leave the parenthesis off of method calls?

Looks?

Typing economy?

Traumatic experiences programming in PHP??

Whatever the underlying cause, there are several reasons why Ruby’s “no parenthesis” convention is bad.

Reason #1: Not Using Parenthesis Can Result in Unnecessary Unexpected Behavior

Consider the following:

class Parent
  def initialize; end
end

class Child < Parent
  def initialize(msg)
    super
    puts(msg)
  end
end

If you try to run Child.new, you’ll get an error:

ArgumentError: wrong number of arguments (given 1, expected 0)

This is because per the Ruby docs:

When used without any arguments super uses the arguments given to the subclass method.

http://www.ruby-doc.org/core-2.2.0/doc/syntax/modules_and_classes_rdoc.html#label-Inheritance

Turns out the solution here is to change the child class calls to super from super -> super() and thus make it explicit that you aren’t passing any arguments to the parent class.

So this is a situation where not using parenthesis can get you into trouble. But Ruby programmers are told:

“When writing code in Ruby, do not* use parentheses.”

* Except for those times that you should use them or, you know, have to use them.

So why not just use them all the time and give yourself one less thing to keep track of? What does using parenthesis all the time hurt? Sure, it’s a few extra tokens to parse when reading, but that leads me to my next point…

Reason #2: Not Using Parentheses Creates Unnecessary Footguns

The main argument for omitting parentheses is that it’s one less thing for programmers to have to parse when reading your code. True; however, it also makes it so that they have to keep track of more things. Just see the example above.

As the saying goes:

Everything should be made as simple as possible, but not simpler.

Albert Einstein

I’d argue that not using parenthesis by default is an instance of Rubyists trying to make things too simple.

Reason #3: Not Using Parentheses Makes Code Less Readable

Take this line of code:

require_template

Is that the name of a variable or a method? There’s no way to tell, not without either searching the code or relying on an IDE. (Note: You shouldn’t have to use an IDE to productively read code.)

In contrast, check out:

require_template()

Much better. You can immediately tell that this is a method that’s being called. Yeah the method doesn’t take any parameters and so yeah you can leave those parenthesis off if you want to…in fact, if you use parenthesis, Rubocop will ding you for it as a stylistic faux paus.

I’d like to use really strong language here, but I’ll content myself with: Oh heck no! The code with parenthesis is 1000x more readable and explicit than the code without, so I don’t care if it’s “uglier” (it’s really not) or that it took two extra key strokes to type. Good developers want to write code optimized for read time, not write time, so laugh at me, call me uncool, do whatever you’d like, but you’ll have to pry my parenthesis out of my cold, dead hands.

Sorry, not sorry.


TL;DR – Sometimes you have to use parenthesis in Ruby, so just go ahead and use them all the time and give yourself one less thing to keep track of.

P.S. Guess what – the Ruby community already inconsistently favors this logic. They just apply it to quotation marks (“Go ahead and just always use double quotes to avoid gotchas”) – Google it and see for yourself.

Papercut #2: Return those Returns to the Store Where You Bought ‘Em

Consider the following:

def initialize_log()
  log = ApplicationLog.new()

  log.timestamp = Time.current()
  log.account = @log_template.account
  log.user = @log_template.user

  return log
end

Rubocop will ding the above code for the parenthesis in the method calls (discussed earlier 😜) and for using an explicit return. It wants that last line to just be log.

Once again, why? Why is using return here a problem? At the very least, as written above, that method will always end at the return. Even if someone inadvertently comes behind and adds more code after return log, it won’t break our method because it will just return. Not so if it’s just a simple log.

Consider this code:

def filter_users(users)
  users.
    reject { |x| x["is_active"] == false }.
    reject { |x| x["is_removed"] == true }.
    reject { |x| x["type"] == API_ONLY_TYPE }
end

Let’s say you want to add some logging to record how many valid users you end up with:

def filter_users(users)
  users.
    reject { |x| x["is_active"] == false }.
    reject { |x| x["is_emoved"] == true }.
    reject { |x| x["type"] == API_ONLY_TYPE }

  log_entry("Fetched #{users.count} valid users")
end

Nice, only guess what? Congratulations – you just broke any code that relies on this method returning the users array! That’s because before the code was taking advantage of the fact that Ruby implicitly returns the last line of a block.

In order to fix the above code, you need to do something like:

def filter_users(users)
  filtered = users.
    reject { |x| x["is_active"] == false }.
    reject { |x| x["is_emoved"] == true }.
    reject { |x| x["type"] == API_ONLY_TYPE }

  log_entry("Fetched #{users.count} valid users")

  filtered
end

But guess what? If you’d just used an explicit return in the first place you could have avoided this edge case altogether:

def filter_users(users)
  filtered = users.
    reject { |x| x["is_active"] == false }.
    reject { |x| x["is_emoved"] == true }.
    reject { |x| x["type"] == API_ONLY_TYPE }

  # Add anything you want here;
  # you can't mess up the return value of this function now

  return filtered
end

“But that’s extra typing.” Yes.

“But you don’t need the filtered or and the return.” Actually, we did end up needing them. Not having them caused a bug. Being a bit more explicit in this case isn’t premature optimization – it’s defensive programming.


TL;DR – In Ruby, leaving the return off gains us nothing and exposes us to possible errors…so why leave it off in the first place??

Papercut #3: Thou Shalt Not Prefix…Because Ruby Core Library

Consider the following column name found in db/schema.rb in a Rails app:

remote_login_user

To what does remote_login_user refer? Is it foreign key to another object? Is it serialized JSON? Nope…it’s a boolean.

A boolean…you know, other languages have conventions for booleans, recommending they be prefixed by is_, has_, etc. But Ruby? No, it allows the super-awesome ? character in method names, so no need for dumb boolean prefixes, just do user.remote_login_user? and it’s really nice looking and totally obviously a boolean bro.

Except what about outside the codebase? How is someone navigating your database supposed to realize that remote_login_user is a flag? You’ve done nothing to point in that direction and the only thing you can hope is that they check the column type before they venture completely off course and assume it’s a foreign key or something.

Conversely, what about these?

is_remote_login_user
has_remote_login_user

Guess what – adding a ? to those names doesn’t make them any less readable:

is_remote_login_user?
has_remote_login_user?

But using a prefix makes it so that anyone browsing the codebase or the database can easily know that those are booleans and probably flags.

But no…Rubocop in its infinite wisdom tells us:

Avoid prefixing predicate methods with the auxiliary verbs such as is, does, or can. These words are redundant and inconsistent with the style of boolean methods in the Ruby core library, such as empty? and include?.

https://github.com/rubocop/ruby-style-guide#bool-methods-prefix

In other words, “How dare you use prefixes (like pretty much every other programming language recommends) and be inconsistent with the boolean methods in the Ruby core library? So smudge…”

Above: A seasoned Ruby dev reviewing my code that uses prefixes

TL;DR – Prefixes make your code and database more readable, so you should just use them already.

Strong Opinions, Loosely Held

Look, I get it. This is all opinion and conjecture akin to vim vs. emacs, tabs vs. spaces, Republicans vs. Democrats, etc. Neither side is 100% right and there are pros and cons. All one can do is seek the stance they feel best balances the pros and cons, many of which are inherent in life itself.

At the end of the day, to me the benefits of these “best” practices amount to terser code with fewer characters readers have to parse but at the expense of causing other readability issues, creating extra gotchas for programmers to keep track of, and handing developers loaded footguns galore.

No thank you. It’s time to show the world that there’s a different way.

We are the Ruby counter-culture. We take time to type a few extra characters if it makes our code’s intentions clearer. We don’t leave loaded footguns lying around in our code for those coming behind us to inadvertently stumble upon. We don’t mind the sideways looks our code gets on StackOverflow. Our motto is:

Parenthesis
Returns
Prefixes

Come – join us!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.