InfoQ

News

Explicit vs. concise code in Ruby

Posted by Werner Schuster on Jul 30, 2007 09:00 AM

Community
Ruby
Topics
Programming
Tags
Language Features ,
Troubleshooting ,
Coding Standards
Piers Cawley writes about a potential problem he discovered in a blog article about Lazily Initialized Attributes. The problematic code:
def content
 @content ||= []
end
This is aimed to allow for lazily initialized attributes of a class. In this case, the @content instance variable is initialized with [] when it's accessor method content is called, unless it has already been initialized. The ||= operator means "if the left hand variable is nil, set it to the right hand value, otherwise just return the left hand variable's value".

However, as Piers points out, there is a problem with certain values due to the way Ruby treats boolean values and nil. Here an example to illustrate:
a = false
a ||= "Ruby"
What's the result of this? Since a was initialized in the first line, the second line should not have had any effect. However, executing that code reveals that a now has the value "Ruby", instead of false.

The problem becomes clear by remembering the common way to write nil checks in Ruby:
if name
 puts name.capitalize
end
In Ruby, a nil is interpreted as boolean false, so the code in the if clause will only run if the name is not nil.

While this is usually not a problem, in the lazily initialized attributes code, it's a problem if a legal value for attribute is either nil or false. In that case, an access would reset the variable to it's default value.

This is certainly a corner case, however it's the kind of issue that can cause long debugging sessions, trying to figure out why some attributes are occasionally reset while others aren't.

Piers offers a more explicit version of the code:
def content
 unless instance_variable_defined? :@content
 @content = []
 end
return @content
end
This only initializes the variable if the variable hasn't been defined yet.

This little example could be blamed on Ruby and some of it's language features - but it's widely known which type of workers blame their tools instead of themselves. While the conciseness  of Ruby is very helpful, there are cases where more explicit expression of intent is safer. In this case, the ||= wasn't the right solution and instead the initialization code is supposed to check if the variable had been defined yet.

Have you been bitten by issues such as this one? Are there Ruby features you like to avoid to prevent subtle errors?

8 comments

Reply

Nice post by Stephan Schmidt Posted Jul 30, 2007 11:55 AM
More Variable Metadata by John DeHope Posted Jul 30, 2007 12:50 PM
Re: More Variable Metadata by matt mcknight Posted Jul 30, 2007 3:23 PM
definitely a corner case. by matt mcknight Posted Jul 30, 2007 1:10 PM
Re: definitely a corner case. by Werner Schuster Posted Jul 30, 2007 1:41 PM
Re: definitely a corner case. by matt mcknight Posted Jul 30, 2007 3:21 PM
Re: definitely a corner case. by Piers Cawley Posted Aug 5, 2007 6:52 PM
You mean I can't just turn off my brain? by Levi Cook Posted Aug 6, 2007 9:29 AM
  1. Back to top

    Nice post

    Jul 30, 2007 11:55 AM by Stephan Schmidt

    Nice post. Those bugs are nasty to find.

    "... it's widely known which type of workers blame their tools ..." makes me laugh though.

    I know a lot of people who blame Java because of their lack of productivity. And I know very few people who still write Mc68k/40 code.

    Peace
    -stephan

    --
    Stephan Schmidt :: stephan@reposita.org
    Reposita Open Source - Monitor your software development
    www.reposita.org
    Blog at stephan.reposita.org - No signal. No noise.

  2. Back to top

    More Variable Metadata

    Jul 30, 2007 12:50 PM by John DeHope

    I've often thought we need more metadata on our variables (or function arguments, or properties, etc). Currently we have just one: null. A reference can either be null, or have a value. I'd like to also have "defined" to say if a given name has been defined. "Initialized" can say if it has ever been set before, in which case the value could also be nil or non-nil. Also "null" can work just like it does now (or nil in Ruby, whatever). A hybrid "has value" concept would be nice too, which requires both "initialized" to be true and also "is null" to be false. With these constructs it would be possible to write very clear expressions such as "if x is initialized ..."

  3. Back to top

    definitely a corner case.

    Jul 30, 2007 1:10 PM by matt mcknight

    def content
    @content ||= []
    end

    In what case could something return an array or false- n valued logic? Seems completely off base as a criticism.
    100% of the time I use code like this it's to avoid doing nil? checks all over the code. If you are writing code where nil is an acceptable value, why would you need to initialize the array to empty? It seems odd to have a semantic difference between nil and empty array where nil is something that the array explicitly gets set to after it is initialized. A programmer would generally expect that nil means uninitialized. Now I have to check for nil every time I call:
    content.each

    In the case where you are actually dealing with a boolean, you don't have to do anything- the nil naturally works in conditional expressions.

    Maybe I just need a better example to understand this particular point. I agree with the general thrust of the article- that sometimes being concise can obscure the meaning of the code, but I don't see this idiom as problematic. On the other hand- having 0 != false in Ruby is a bit weird, no pun intended.

  4. Back to top

    Re: definitely a corner case.

    Jul 30, 2007 1:41 PM by Werner Schuster

    The original article on "Lazily Initialized Attributes" ( blog.jayfields.com/2007/07/ruby-lazily-initiali... ) where this issue was found, shows a general way to initialize attributes only when they're actually accessed.
    So, this is not specific to lists, but generally to using this idiom for initializing attributes.

    So, if you use this idiom to create a boolean attribute or an attribute that can be some value OR nil, then you can run into trouble.
    Eg


    class Foo
    def red?
    @red ||= true
    end
    def red=(arg)
    @red = arg
    end
    end

    x = Foo.new
    x.red? # returns true, default value

    x.red = false # @red is now false

    x.red? # returns ... true, because of the issue described in the article

  5. Back to top

    Re: definitely a corner case.

    Jul 30, 2007 3:21 PM by matt mcknight

    The trickiness in Jay's case is trying to lazy initialize a boolean to true. That's the only place one could actually run into a problem, but a simple nil check would work there. I still think the standard

    @x ||= default

    works except for cases where you want to initialize a boolean.

    In any case, lazy initialization should probably only used for expensive operations, not for setting simple defaults.

  6. Back to top

    Re: More Variable Metadata

    Jul 30, 2007 3:23 PM by matt mcknight

    I think the distinction between nil? and empty? or blank? (a Rails addition) is more interesting area to explore. I think using or implementing those methods makes things more clear than setting something to nil and expecting that to mean empty, as opposed to uninitialized.

  7. Back to top

    Re: definitely a corner case.

    Aug 5, 2007 6:52 PM by Piers Cawley

    My point was that generic code should always consider the corner cases. Espcially with a pattern like this which can so easily be wrapped up in a method constructor. In code where you control the horizontal and the vertical there's nothing wrong with taking the view that you'll never trip over the nil/false problem, but in library code you must account for it.

    Since a pattern is essentially a subroutine that's executed by a human being, it makes sense for the pattern to at least discuss the potential pitfall.

  8. Back to top

    You mean I can't just turn off my brain?

    Aug 6, 2007 9:29 AM by Levi Cook

    I have to admit to using ||= absentmindedly. Thanks for pointing out the gap in this idiom. I'm sure it was only a matter of time before I was bit by it.

Exclusive Content

Book Except and Interview : Aptana RadRails, An IDE for Rails Development

Aptana RadRails: An IDE for Rails Development by Javier Ramírez discusses the latest Aptana RadRails IDE, a development environment for creating Ruby on Rails applications.

Fast Bytecodes for Funny Languages

Cliff Click discusses how to optimize generated bytecode for running on the JVM. Click analyzes and reports on several JVM languages and shows several places where they could increase performance.

Scott Ambler On Agile’s Present and Future

Scott Ambler, Practice Lead for Agile Development at IBM, speaks on the current status of the Agile community and practices having a look at the perspective of the Agile’s future.

Manager's Introduction to Test-Driven Development

Dave Nicolette and Karl Scotland try to introduce non-technical managers to one of the most popular Agile development techniques: Test-Driven Development (TDD).

Structured Event Streaming with Smooks

Smooks is best known for its transformation capabilities, but in this article Tom Fennelly describes how you can also use it for structured event streaming.

How to Work With Business Leaders to Manage Architectural Change

Successful architectures evolve over time to meet changing business requirements. Luke Hohmann presents how to collaborate with key members of your business to manage architectural changes.

Colors and the UI

In this article, Dr. Tobias Komischke explains how colors used in a GUI can influence our interaction with a computer and offers advice on using the appropriate colors for the interface.

Building your next service with the Atom Publishing Protocol

In his presentation, recorded at QCon San Francisco, MuleSource architect Dan Diephouse explores ways to use the Atom Publishing Protocol (AtomPub) when building services in a RESTful way.