The maze book for programmers!
mazesforprogrammers.com

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Brevity vs. Clarity

5 January 2007 — 2-minute read

Today’s TADFALAICKIU: balancing brevity and clarity.

I’m a big fan of the ternary operator. (That’s the bizarre little foo ? bar : baz construct which Ruby, and others, borrowed from C.) It lets you express simple conditionals very concisely. However, the poor ternary operator gets a lot of flak. It is easily abused, and some people have even gone so far as to swear that any use of it is an evil use.

If that’s you, then I guess we’ll have to agree to disagree. :)

I’ll admit that I’m still learning the balancing act. I’ve been going through some older code of mine and discovering things like the following:

1
2
3
4
5
def smart_overview_url(options)
  @project ?
    project_overview_url(options.merge(:project_id => @project.id)) :
    overview_url(options)
end

Although certainly not one of the evilest invocations of the ternary operator, it is undeniably destined for some circle of Hell. Ternary Operator Rule #1: if you can’t fit the entire condition on a single line, use an explicit if-then-else construct.

1
2
3
4
5
6
7
def smart_overview_url(options)
  if @project
    project_overview_url(options.merge(:project_id => @project.id))
  else
    overview_url(options)
  end
end

There, isn’t that cleaner? Other rules of thumb:

  • If you need to nest a ternary operator inside another ternary operator, you’re almost certainly doing it wrong. Nested ternaries are hard to read, and that’s bad. Break it out. Be verbose.
  • If you need to parenthesize any of the component parameters of the operator, you’re almost certainly doing it wrong. Parentheses add line noise to a sequence that already has quite a bit. Break it out. Be verbose.

So, when is it ok to use the ternary op? Try these examples:

1
2
3
4
5
6
# trivial conditions
increment = x < y ? 1 : -1
# simple type conversions
value = record.respond_to?(:something) ? record.something : nil
# unfolding hashes
element = hash[:here] ? hash[:here][:there] : nil

Note that in the case where something is either a result of some operation or nil, you can also use the && operator in Ruby:

1
2
3
4
# simple type conversions
value = record.respond_to?(:something) && record.something
# unfolding hashes
element = hash[:here] && hash[:here][:there]

That works because && does not (necessarily) return a boolean value—it returns the first false/nil value, or the last value if none are false/nil.

Reader Comments

Thanks Jamis, I’ll put the && operator to use immediately!

Jamis, congratulations for yet another great article! Your site is on my “must read daily” section.

I have to disagree on the usage of the && operator though, it is a very interesting usage but, considering the article title “Brevity vs. Clarity”, it seems to be a lot more on the Brevity side :-).

Silvio, that brings up a good point because the && is very clear to me … because I’m used to it. While there are certainly rules of thumb for clarity, sometimes it’s very subjective. It’s difficult (and important) to strike a brevity/clarity balance that fits for your expected audience.

Silvio, as Dave said, familiarity aids readability. I’m curious, though: how would you rewrite the “hash[:here] && hash:here” example? Would you really use a full-blown if-statement?

Too much syntax can be just as bad as too little for readability. You need to strike a good balance.

The && property we’re talking about here is called Short circuit evaluation. If someone wants to do some more reading.

I have to say that I rarely use the ternary operator. I find that almost always it breaks the flow reading of a piece of code. I always have to read it twice to find out if the ? is a ternary operator or a ? at the end of a query method.

Of course in ruby an if statement has a value so you can do:

element = if hash[:here] then hash:here else nil end increment = if x < y then 1 else -1 end

which with syntax highlighting is infinitely more readable than the ternary (IMHO). The presence of the ‘if’ immediately tells you that it is a conditional expression, rather than having to work out if the ? is a query or not. Sick example:

element = arr.empty? ? “empty” : arr.first element = if arr.empty? then “empty” else arr.first end

Let’s try that again with line breaks (comment systems – grrrrr)

element = if hash[:here] then hash:here else nil end

increment = if x < y then 1 else -1 end

and

element = arr.empty? ? “empty” : arr.first

vs.

element = if arr.empty? then “empty” else arr.first end

Bah. You’re right Jamis. Blast ! I hereby find myself guilty of excessive ternary usage.

Please keep up these TADFALAICKIU articles… they are brilliant.

I’m a rescue fan, when it comes to saving code space # simple type conversions :
value = record.something rescue nil

Jamis and Dave, you are right about the familiarity, being fairly new to Ruby and coming from a C/Java background, the && operator always sounds like it will return a boolean (or 1/0) back. It will take me a while to get used to this.

Answering your question, I would stick with the ternary operator as, for me, it is concise and explicit. Alain solution also sounds very sweet, though it is a little generic as any raised exception would be caught by the rescue, if believe.

Isn’t it better to use exceptions for exceptional conditions, rather than flow control? I guess it depends on whether you expect record.something to have a value or not.

John, I’m with you. If you expect that record may well not respond to the something method, using rescue to handle that case is a bad idea. Exceptions are for exceptional conditions, those cases that shouldn’t occur, but might. Also, as Silvio pointed out, using rescue in that case can hide a host of unexpected exceptions, and introduce bugs in your code.

Why not use the statement modifier syntax?

# simple type conversions
value = record.something if record.respond_to?(:something)
# unfolding hashes
element = hash[:here][:there] if hash[:here]

That last one could even read like this, which I believe is more descriptive:

element = hash[:here][:there] if hash.has_key? :here

What? You don’t like the ternary operator? Back when I was a n00b coder, I loved it ;)


cam.a.x = -sin(camrot.p.y*2.0*M_PI)*(direction&(forward|backward))*((direction&backward)?-1.0:1.0) + cos(camrot.p.y*2.0*M_PI)*(direction&(strafeleft|straferight))*((direction&strafeleft)?1.0:-1.0)*0.4;
cam.a.z = cos(camrot.p.y*2.0*M_PI)*(direction&(forward|backward))*((direction&backward)?-1.0:1.0) + sin(camrot.p.y*2.0*M_PI)*(direction&(strafeleft|straferight))*((direction&strafeleft)?1.0:-1.0)*0.4;

Daniel, you’re right, that will implicitly set the variable to nil if the condition is false…but in general I shy away from the implicit assignment of local variables. Feels like too much magic, but that’s just my preference.

Another way to do the hash:

element = hash.fetch(:here, {})[:there]

Not quite as clean as some already mentioned, but thought I’d throw it out there.

Jamis, a great series, and interesting discussion on this point. A few thoughts…

1. I tend to agree with you and John about reserving the rescue usage for true exceptions.

2. A couple co-workers and I discussed this post. We have been in the habit of using the if modifier proposed by Daniel above, but after reading your comment, it dawned on me that the implicit nil value we were relying on is really an implicit non-assignment (rather than assignment to nil), which could also quickly kill the ‘magic’ you alluded to. Consider:

element = 'something' # (later)... element = hash[:here][:there] if hash[:here]

In this case, element is still 'something'. We generally intended it to be nil after the assignment (unless we really meant “only run this assignment ‘if’, otherwise keep any previous value”). So we decided to reserve our use of the if qualifier for circumstances where we really mean “only run this logic if __”, and use your nifty && usage if we really mean “make an assignment/reassignment and use nil if you can’t get to the value”.

3. The if/then/else did feel better to me than the multiline ternary, particularly in your example where it is wrapped thinly in the method. For some reason though, a direct assignment in context (not in a method as above…which often is a better idea) “feels” better as a three line ternary:

url_i_want_to_use = @project ? project_overview_url(options.merge(:project_id => @project.id)) : overview_url(options)

Putting the url_i_want = assignment explicitly in each branch seemed decidedly un-DRY, yet assigning it to the return of a longer if statement actually seemed to lose some of the clarity in the ternary, albeit a 3 line ternary.

Oops, code did not format as expected, but think you know what I meant (retry…)

url_i_want_to_use = @project ? <br/> project_overview_url(options.merge(:project_id => @project.id)) : <br/> overview_url(options)