I originally learned a lot of these ideas from Confident Ruby, one of my favorite Ruby books. If you like this post, you should buy it and read the entire thing. There’s so much good stuff in there.

Your current_user method returns a User, except when there is no user and it returns nil. A search method returns an Array of results, unless there’s only one result, and it returns just that result instead. Seems reasonable, right? Maybe even convenient!

But soon, these decisions will bury your code under a mountain of if statements. Maybe it’s a bunch of if kind_of? sprinkled all over. Or maybe you feel like you have to check for nil everywhere. Or worse, NoMethodErrors start showing up whenever you ship a new feature. Guess it’s time for another hotfix!

There is a way to prevent this, though, and all it takes is a little thoughtfulness.

The Robustness Principle

There’s a principle in computing that says,

Be conservative in what you do, be liberal in what you accept from others.

You can apply this principle to your Ruby methods. The methods you write should accept reasonable input, and should return consistent output.

Focusing on the last part: When someone calls a method you wrote, they should know exactly what that method will return.

Be thoughtful about your output

Take a look at Rails 2.1’s implementation of ActiveRecord::Errors#on:

1
2
3
4
5
6
7
# File activerecord/lib/active_record/validations.rb, line 212
def on(attribute)
  attribute = attribute.to_s
  return nil unless @errors.has_key?(attribute)
  errors = @errors[attribute].map(&:to_s)
  errors.size == 1 ? errors.first : errors
end

When called, this could return either an Array of Strings, a String, or nil. It’s up to the caller to figure out which type of object it’s dealing with. This is a bad idea:

  • The caller has to muddy up its own code with obtrusive type-checking.

  • The caller has to know a lot about the method it’s calling. At a minimum, it needs to know every type of object the method could return and when each type could be returned.

  • You have more edge cases to test. If you want to be confident that your code is doing what it’s supposed to do, you have to try out all three scenarios.

Your methods should be consistent about what they return. If you usually return an Array, do what you need to do to always return an Array. If you usually return a User, but sometimes return nil, you could create a Null User object and return that instead of nil.

You could even be less strict: “I’m going to return something that includes the Taggable module”. You could go more generic: “I’m going to return something with id and name attributes.” The important thing is consistency and making sure your caller knows what to expect.

jQuery is an interesting example. Most jQuery methods return the same kind of Array-like object. Because of this, jQuery’s methods are incredibly composable, and you can do crazy amounts of work in a single line of code.

And in case you were wondering, Rails fixed that method in later versions:

1
2
3
4
# File activemodel/lib/active_model/errors.rb, line 133
def [](attribute)
  get(attribute.to_sym) || set(attribute.to_sym, [])
end

Now it always returns an Array. Simpler for them, and simpler for us.

Kill inconsistency

The next time you find yourself returning “An Array or nil”, just return an Array. Take a look through your codebase and see where you’re using kind_of? and respond_to?. See if you can refactor the methods called by that code to return a single type.

And watch as the assumptions you can make about your return values ripple through your project and simplify all of your nearby code.

Don't miss out on my next essay

Sign up below to get my free weekly Ruby column. I'll send you original articles and advice every Friday to help make you a smarter, better Ruby developer. Drop your name in the box!

Did you like this post? You should read these:

Comments