Why, if reading Ruby is like reading natural language, unless it’s not?

Yesterday, a familiar Ruby coder showed me a piece of Ruby code that used Ruby’s programmer-friendly if-construct, where you can place the if clause after the conditional block of code. It was something like this:

range.each do |n|
    if n == 1
        # code
    elsif n == 2
        # more code
    elsif n == 3
        # even more code
    elsif n == 4
        # you get the drift
    elsif n == 5
        # and there were still more ifs here doing all kind of things
    end
end if some_var == 'gotcha!'

And all this was embedded in some more nested loops ‘n’ ifs. Why, why, why would someone want to code like this? It’s unreadable. It’s unintuitive. It forces you to read up and down and back again. You might get lost in the forest of ifs and elsifs when all the while the loop would never be executed because there’s a catch in the very last line.
Don’t get me wrong, I wouldn’t go so far as to call this bad coding. It’s a matter of taste. The guy who wrote this knows what he’s doing (most of the time). He thinks it’s neat and clean. I don’t, obviously. What’s wrong with just one extra line of code, but a whole lot more readable:

if some_var == 'gotcha!'
    range.each do |n|
        if n == 1
            # code
        elsif n == 2
            # more code
        elsif n == 3
            # even more code
        elsif n == 4
            # you get the drift
        elsif n == 5
            # etcetera
        end
    end
end

Sure, it’s extra indentation. But it’s a conditional block! It _should_ be indented!

In Why’s (Poignant) Guide to Ruby, Why presents the following example of the deferred-if-construct, to show how much Ruby is like ‘natural language’:

exit unless "restaurant".include? "aura"

Which reads, according to Why, like the English sentence: “Exit unless the word restaurant includes the word aura”. But why, Why, would you want to read it like you read English? We’re all programmers! We can all read a programming language! We’re all used to reading a good old plain if construct, like:

if (!"restaurant".include? "aura")
    exit
end

There, that got rid of that horrendous unless as well. What’s unless anyway? It’s nothing but ‘if not’. It’s confusion waiting to happen. Just take a look at this:

if "restaurant".include? "rant"
    unless "restaurant".include? "aura"
        exit unless "restaurant".include? "rest"
    end if "restaurant".include? "esta"
end unless "restaurant".include? "taurus"

Enjoy this fine piece of ‘natural language’!

2005-11-29. 3 responses.

Comments

  1. You make a good point about much of the example code you list. However, I think that in the case of single lines of code the construct makes sense. Adding additional conditionals to blocks of code goes against my code asthetic as well, and I even itch a bit at adding .methods after blocks, perhaps simply because I don’t expect them there and fear that I shall miss them. Example of what I mean:

    [1,2,3].collect do |element|
    element*2
    end.reverse

  2. Sure, in a single line construct it’s not so bad… I remembered afterwards that my own point was used years ago against the Pascal repeat…until statement (which I never hesitated to use). In Pascal, the while statement was said to be preferred, because you can see the loop condition beforehand. When learning a new language, I’ve always had the tendency to ‘bring along’ things from previous languages. It will pass. I really like Ruby!

  3. Generally I agree with your point – the trailing ‘if’, if used at all, should be on one-liners only.

    But what really raises a smell in the given example is the endless string of elseifs, begging for refactoring.