Ruby 101: Transparent Code 19

Posted by jeff Tuesday, May 22, 2007 05:57:00 GMT

Window

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.

Comments

Leave a response

  1. el raichu   May 22, 2007 @ 07:45 AM

    don't forget rescue

    SomeRecord.find( x ) rescue SomeRecord.find( y ) rescue SomeRecord.new
    

    sheesh... no preview comment... oh well...

  2. Frank   May 22, 2007 @ 10:17 AM

    Nice article!

    I love modifiers as well. Combined with methods that ends with a question mark, it helps producing some nice english-like sentences.

  3. Jeff   May 22, 2007 @ 01:53 PM

    @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.

  4. mjgiarlo   May 22, 2007 @ 03:39 PM

    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.

  5. Jason   May 22, 2007 @ 06:23 PM

    Great post! Coming from the MS world I need all the help I can get flushing out the "old ways".

  6. Jeff   May 22, 2007 @ 07:33 PM

    @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.

  7. Michael Boutros   May 22, 2007 @ 08:08 PM

    Very good post Jeff, definitely learned a thing or seven!

  8. MajorTom   May 24, 2007 @ 03:45 PM

    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-

  9. David   May 24, 2007 @ 05:56 PM

    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.

  10. carmelyne   May 24, 2007 @ 07:33 PM

    Nice write up. It springs into a much readable and maintainable code.

  11. Jeff   May 25, 2007 @ 01:37 AM

    @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.

  12. David   May 25, 2007 @ 02:52 PM

    @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.

  13. Biennale   May 29, 2007 @ 09:05 PM

    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? end

  14. kane   October 03, 2007 @ 01:38 PM

    It 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.

  15. Geoff   October 03, 2007 @ 07:19 PM

    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

  16. Pradeep Jain   October 12, 2007 @ 07:35 PM

    (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.

    English  -    The dog bit the man.  ( subject action  object)
    Hindi - kutta ne aadmi ko kata - The dog man to bite - (subject object prep action)
    

    (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

             dog bite man         as         man bite dog  :)
    

    --- Cheers

  17. Miguel Madero   February 11, 2008 @ 05:27 AM

    @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.

  18. kino   May 24, 2008 @ 01:14 AM

    The Ideal of human reason (and we can deduce that this is true) can not take account of the objects in space and time.

  19. Ellroy   May 30, 2008 @ 11:23 PM

    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.

Comment