Let’s Talk About Conditional Expressions

by Evan Light on December 21, 2011

This guest post is by Evan Light a test-obsessed developer, the author of several rarely used gems, and the curator of Ruby DCamp. When he’s not a talking head at conferences, he’s usually working at home as a freelance developer remotely mentoring a developer, working for one or more startups, playing with open source, keeping his wife and four cats company, hacking nonsensically, talking at people on the internet, and/or attempting to lose weight (or any combination of the above). What else do you do when you live 3 hours from civilization?

Evan Light You know what bugs me? People who don’t write idiomatic conditionals in Ruby.

Many folks, especially those who came out of Java, .NET, or C/C++, use and abuse the ternary. The ternary is a humble little production rule that works like so:

<boolean expression> ? <eval this expression if truthy> : <eval if falsey>

The ternary can be useful if the truthy and falsey cases are terse and well-named. However, it’s common to see multi-line ternaries or, even worse, nested ternaries. For example:

By the way, the above example comes out of Rails.

Accepting that the above is just painful, how could we make it hurt less? Specifically, let’s focus on the nested ternary on Line 7 (accepting that there are other ways that the code can be made clearer).

For starters, let’s DRY up these Hash lookups:

Now let’s unravel this into more idiomatic Ruby. We’ll extract the outer ternary into a functional style if-else. Remember that every expression in Ruby returns the value of its last executed expression. The if-else returns either the value of the computed String in the if or the empty String else. It has the advantage of containing more English words and fewer characters that resemble modem line noise or PERL.

We can get rid of this else. After all, it doesn’t contain any logic. Let’s just make it the default value for method_prefix and then assign a new value only when prefix is truthy.

Ah so what this code really cares about, first and foremost, is whether prefix is falsey. When it’s falsey, it just returns an empty String! Otherwise, if it’s not, it generates a String possibly using the value of prefix if and only if prefix is not true – not a boolean.

So why should we try to avoid ternaries? Because, when used within a complex expression, they tend to lack the clarity of intent of an if-else. The lack of clarity likely resulted in the less DRY previous implementation. Once we introduced more clarity, it became obvious how this code could be DRY’d up, resulting in better readability.

I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Thanks!

Subscribe to the waiting list of the free, online “Intermediate Ruby Course“. As a bonus, get a free copy of the “Introduction to Rack” eBook (.pdf format).

Technorati Tags: , ,

Posted by Evan Light

{ 4 comments… read them below or add one }

trans December 21, 2011 at 12:32 pm

Having logic in strings is a bit ugly too, so we might also do:

if prefix
method_prefix = “%s_” % [prefix == true ? to : prefix]
end

Also, another approach:

method_prefix = (
case prefix
when false, nil then ”
when true then #{to}_”
else #{prefix}_”
end
)

Not as concise, but very easy to comprehend. (Note, I would align things a bit better than this comment box easily allows.)

Reply

Evan Light December 31, 2011 at 2:40 am

Excellent point. While I’m not a fan of using case statements with booleans, I am *loving* using them to switch on symbols. Far more expressive than if-else.

Reply

Rodrigo Rosenfeld Rosas December 21, 2011 at 6:49 pm

I would probably write this as:

def delegate(*methods)

prefix, to = methods.pop.values_at(:prefix, :to)
prefix = to if prefix == true

method_prefix = prefix ? “#{prefix}_” : ”

end

Reply

Wes December 22, 2011 at 4:20 am

I feel the same way about using #map when there is no intent on using the final array. You should then just use and each loop.

Reply

Leave a Comment

{ 15 trackbacks }

Previous post:

Next post: