Ruby 101: Transparent Code 19

Photo credit: http://flickr.com/photos/worldofoddy/
One of the reasons I love Ruby is that my code often reads like English. When my code becomes as readable as an English sentence, I find it more likely that I'll code exactly what I want to achieve. Code is easier to understand and maintain when the original intention of the code is self-evident. I think that's partly why Java and C# were so attractive as compared to C++. And why C++ was so much more fun than C.
Ruby is a large language, just like Chicago is a big city. But today I'd like us to venture down a side street, to take a road less traveled by Ruby newcomers. We will learn how to use the modifier form of some well-known Ruby keywords to improve your code's clarity.
Say Cheese
Let's say we're writing code to operate a digital camera. You've got methods like capture_image, for example, which will capture an image from the sensor array and generate an image file onto the memory card (and if we're lucky, emit a silly "click" sound to imitate an analog camera). Let's look at how you might write the code for when the user presses the shutter button:
def shutter_clicked
if @camera.off? || @camera.memory_card_full?
return
end
capture_image
end
So if the camera is off or we're out of memory, we just bail. If all is good, we take the picture. This was my style when I started with Ruby, and if that code looks like how you would do it too, then you're in for something new and rewarding.
The key is to know that keywords like return, while, if, unless, and until can be used as modifiers. This means that you put the conditional part after the keyword. Sounds silly, and sometimes it is. But sometimes it has a big payoff.
The code we wrote was trying to capture some requirements. In order to take a picture, the camera can't be off and the memory card can't be full. Using the modifier form of return, you can rewrite the above code like this:
def shutter_clicked
return if @camera.off? || @camera.memory_card_full?
capture_image
end
The first line says, return right away if the camera is off or the memory card is full. If we get as far as the second line, we can go ahead and capture the image. Read the code out loud. Sounds better, right?
But wait, you can go a step further. For example, you might prefer this:
def shutter_clicked
capture_image unless @camera.off? || @camera.memory_card_full?
end
Now read it out load again (under your breath if you're in a small cubicle). This expresses the intention a bit differently. When read aloud, the first thing we hear is "capture image". I find it helpful to hear that first, because that's what happens 90% of the time when the shutter is clicked. By writing the code this way, we emphasize the expected behavior. We use the unless clause to guard the behavior with our business rules. The code is more transparent, making the requirements easier to see. This code is easier to understand and maintain.
But let's go back to the original code for a minute. There's no right or wrong way to do this, and perhaps you didn't like my original example in the first place. After all, it's got the awkward-looking return statement hanging out in space. By flipping the logical operator around, the original code could have looked like this:
def shutter_clicked
if @camera.on? && @camera.memory_available?
capture_image
end
end
This reads better already. But we still have the if-then feeling in there. Sounds more like code, and less like English. How about this?
def shutter_clicked
capture_image if @camera.on? && @camera.memory_available?
end
Either way, it's up to you. I wrote Ruby code the "old way" for a long time, and it's only in the last six months or so that I've switched to the modifer forms. Just use the form that seems more natural to you.
Like It's 1999
Now suppose you get a new requirement that the camera should have a feature to take pictures non-stop in strobe-like fashion until every byte of memory is used up. (You ask your customer, why, oh why would anyone want this feature, but it's a "show-stopper" requirement, a "must-have", a feature that the camera "absolutely cannot ship without". So you press on.)
It actually turns out to be an easy method to write:
def go_crazy
while (@camera.memory_available?)
capture_image
end
end
Again - you've been writing code like this for a long time, and especially if you're coming from VB.NET or C# or Java, this code probably seems perfectly readable to you.
Your problem is that you've been in the dungeon so long, you believe that the torch on the wall provides enough light. What if you could escape to the outside and see the sun? Behold:
def go_crazy
capture_image until @camera.memory_card_full?
end
or if you prefer,
def go_crazy
capture_image while @camera.memory_available?
end
Just Do it
The modifier forms aren't just niceties or afterthoughts in the language. They are there for good reason. Try them for a while and see if it doesn't improve your style. Use them whenever and wherever it seems appropriate to you. Make the intention of your code clear and obvious.



don't forget
rescuesheesh... no preview comment... oh well...
Nice article!
I love modifiers as well. Combined with methods that ends with a question mark, it helps producing some nice english-like sentences.
@el raichu: Technically that's not a modifier form of
rescue, although it may look like it. The right-hand side of the rescue is not evaluated first, as it is with the modifier forms that I demonstrate in the article. Perhaps that's an important point that I neglected to point out in the text.But you're right that the inline rescue syntax can often lead to clearer code. Thanks.
Good post; the more Rubyish Ruby code looks, the better. :)
One quick question, though. If you're shooting for code that reads like English, why not use "and" and "or" instead of "&&" and "||"? The text operators do have a lower precedence, but that should not pose a problem for your examples.
Rock on.
Great post! Coming from the MS world I need all the help I can get flushing out the "old ways".
@mjgiarlo: Some people do use the "and" and "or". For some reason I'm not comfortable with them yet. If they had the exact same precedence as
&&and||, then I would. I guess I'm worried that I'll forget, and I'll introduce a bug that I will never find.But yes, I think that's a perfectly good thing to do. Good idea.
Very good post Jeff, definitely learned a thing or seven!
Excellent explanation of Ruby. Recently, I've started looking into this and you have clearly explained it. I understand the first code example because most programmers will go into Ruby and code like that because they understand it. Yet with an example like yours, that clearly states the transitions that "can" be taken to make the code easier to read, I understand the religious zealotry that has come from the Ruby encampment. Excellent post. -T-
Got here from Scott Hanselman's blog. I am just beginning to explore Ruby, and I have to say that I don't get all those excitement over "expressiveness". I don't find C# code any more difficult to read than Ruby. Sure, it doesn't look like English, but why should it? Its NOT English!
As a case in point, you take
if @camera.off? || @camera.memorycardfull? return end
and change it to
return if @camera.off? || @camera.memorycardfull?
and then say "Sounds better, right?"
Quite simply, no. Maybe I have been programming so long that programming if constructs are just as ingrained in me as speaking English. But I just don't see any advantage of the second over the first. In fact, I would argue that the second is worse, since having "return" as the first part of the statement makes you think the function is returning; you have to finish reading the whole statement before you realize that might not actually happen. The order of the statements is reversed from the order of execution, making the whole thing harder to read.
I'm sure some might read this and say "He just doesn't get it." And I guess you're right. I don't get why making a programming language, which is designed to bridge the gap between human thought and computer binary language, look like a particular spoken language (English) is a good thing.
Nice write up. It springs into a much readable and maintainable code.
@David: If you're new to Ruby, then yes, this topic might not seem to make sense. Like I said in the post, even I only "got it" about 6 months ago. And I'm not suggesting you should always use the modifer forms - only use them when they help convey the intent of the code better than before. Write Ruby code for a while, then write a couple of tiny Rails apps, and I think you'll find the modifier forms to be really helpful in many situations.
@Jeff: Thanks for your response. I guess I am just trying to understand what about the second version you think is better than the first. You don't give any reason in the post, you just state that it sounds better. Why is reversing the statements from their natural order of execution a good thing? To make it sound more like english? The first version is close enough to english for me. The only thing missing is an explicit "then", but that is automatically implied to anyone who is used to writing if statements.
I would love to just keep writing Ruby code hoping that the light suddenly comes on, but frankly I just don't have the time. There are lots of technologies out there that I have to keep up with, and if I can't see the value in something quickly, I can't afford to spend time on it. The first day I started reading about .NET, before I wrote a line of code, I recognized its enormous value and knew that it was something I wanted to dive into. Looking at Ruby, I don't see any value that I don't get from .NET. I have been reading blogs for the last week trying to find someone who can show me what I am apparently missing, but so far I have come up empty.
Nice review of different styles in different times. I prefer with if operator. But this one listed below is more readable. Otherwise you need time to get it fast
def shutter_clicked captureimage unless @camera.off? || @camera.memorycard_full? endIt really is a matter of preference. It is great to familiarize yourself with different programming languages and it is great if you prefer one over the other. But don't drink the cool aid.
I'm just starting to learn Ruby here.
I really like the idea of phrasing code such that the most likely outcome is listed before the exceptions...
captureimage unless @camera.off? || @camera.memorycard_full?
captureimage or alerterror
(1) As a SFO bay area consultant I switch from C, C++, Java, Php, Python and RoR. Regardless of language, simplicity for long logical blocks is a key issue. Remember fortran 'flow charts' - well - to make good error handling and readable code, often lengthy if then else nested braces are hard to read. What I encourage my juniors/freshers in C/Java, etc is to minimize the blocks and nesting. This often means converting 3-4 lines of if then else code to a single liner. So the code reader's attention is focussed on the MAIN theme. So from that point the following meets the compactness .
def action captureimage unless @camera.memorycard_full? # verb parms condition end
(2) Human Naturalness. Currently I am so motivated by RoR that I am trying to get a project together to train 1000 BCA,MCA, and even high schoolers in the Delhi, NCR India on RoR and write materials in Hindi. So I would like to make Ruby read more like Hindi, which is interesting because it is syntactically different. e.g.
(3) I am not sure what the sequence is for other human languages, like spanish chinese, etc. But I think the point is that computer programs and human readability will never quite match up. Also the desire for compactness (less lines of code) is against the heavy redundancy built into human languages (the, ne, ko, etc ..).
In a sense it is good that the language syntax is different, because if you have a chinese, hindi, and english guy on same project, one of them possibly could interpret
--- Cheers
@Pradeep, interesting comparison, I remember someone said that the Rails implementation for Japan is a lot different and Rails&Ruby devs put so much effort in making the language/framework syntactically similar to English and other group did the same for Japanese. Table pluralization is a great example of this difference between Japanese and English.
We have several syntactic differences between Spanish and English, but nothing that would apply also to programming languages syntax.
I can see how Ruby readability could make it an easy language to learn for new programmers, but by the other hand, intellisense makes everything easier to discover.
The Ideal of human reason (and we can deduce that this is true) can not take account of the objects in space and time.
It is plain that the repeatable act of grounding is a clarification of the striving for cognition; we now shift the weight of transcendental evidence of cogitationes (if we maintain this attitude) from the transcendental ego to noematic descriptions.