Command-Query Separation in Ruby

One of the features of Ruby that can cause some confusion is that just about everything is an expression. This means that nearly everything that can return a value, does. This leads to some very interesting and sometimes confusing code.

weather = if season == "Winter"

The previous example is contrived because you would typically write it using the ternary operator, but you get my point. Things get even more interesting when it comes to method definitions. Take a look at this method, what does it return?

def lock_door
  @door_state = :locked if @door_state == :closed

It would return :locked if the door was closed, otherwise it would return nil. Is this useful? Not really. What should we do here? Always return nil regardless of if the action was taken or not? Return the final state of the door? I'd propose neither. Leave the method how it is. Bertrand Meyer's principle of Command-Query Separation states that every method should either be a command that performs an action or a query that returns data to the caller. Martin Fowler has a slightly different take on the matter. He prefers to call commands "modifiers" because they are changing the state of the object they are being called on.

To apply this principle to Ruby I like to think in term of grammar. If a method is a verb you shouldn't care what it returns. If a method is a noun or adjective, it had better not change any state.

Like most rules, they are meant to be broken. Martin mentions popping from a stack as a good example. You don't need to look any further than Rails to find some other nice caveats:

p customer.errors unless customer.valid?

The valid? is a query, yet it mutates the object by populating the errors array. I think this is a very pragmatic decision. If we were to refactor this code to follow Command-Query Separation to the letter, it would look something like this:

p customer.errors unless customer.valid?

The problem is, you would almost never have a reason to call validate and never call valid? to see if it worked correctly. Fine choice Rails. Here is another caveat:

customer = Customer.create! :name => "Tony"

This is also a pragmatic approach because if create! did not return the object, we would have to make another round trip to the database to retrieve it.

Command-Query Separation is an important thing to keep in mind. This is doubly true when writing Ruby code because every method returns something whether you like it or not. So if you are going to break the rule, have a good reason.

Update: fixed some typos
blog comments powered by Disqus