Now 'and' for something completely different.
Posted by Uncle Bob on 06/26/2008
My son Justin is working as a Ruby apprentice for my son Micah at 8th light. We were sitting at the kitchen table, and he showed me a function he was writing. In the midst of the function I saw this:
handle_batch(item) and display_batch(item) while items_remaining?
I looked hard at this and then I said: “Justin, I don’t think you understand what and does.
He said: “I think I do.” and he pointed me to a website which showed 21 Ruby tricks “you should be using in your own code.”
Trick #9 was the use of the and keyword to couple statements together to make “one liners”. It was billed as a trick that [cough] “more confident” Ruby programmers use.
So I got the pickaxe book out and looked up the and keyword. I showed Justin where it said that the second clause won’t be executed if the first clause is false (or nil!) So if handle_batch ever returned nil, then display_batch would never be called.
Warning bells were going off in my head. This was clearly an unintended use of the and keyword that could have rather nasty repercussions. I thought it was somewhat irresponsible for a website that boasted expertise in programming to tell people they should be using a dangerous stunt like this.
However, handle_batch did not return nil, so the and worked well enough in this case; and we had more to do. So I made my point to Justin and then we kept working, leaving the and in place.
An hour later we were making changes to a function. Suddenly a whole bunch of our tests broke. (You know what I’m going to say, don’t you?) The failure mode didn’t make any sense. Suddenly a whole bunch of processing simply wasn’t getting done.
It was Justin who said: “Wow, I’ll bet it’s that and. And it was. The change we had made had indirectly caused handle_batch to return a nil.
OK, this was a fun little story. You might think the moral is “don’t use and for one-liners” or “don’t trust websites that claim expertise”. Yes, those would be good conclusions to draw. But I’m concerned about something else.
The “and trick” is clever. In programming, clever != smart. (OK, sorry, that was clever…) Using a keyword like and in a manner for which it was not intended, and in a way that is not guaranteed to work in all cases, is risky. It bothers me.
By the same token it bothers me that so many Ruby programmers use the ||= trick for lazy initialization. I know it works. I know it’s a standard idiom. I’m not trying to stop people from doing it. Hell, I use it too because it’s become a standard idiom. But it bothers me nonetheless because it entered our vocabulary of idioms because it was clever trick; and clever little tricks have a way of turning into nasty little surprises.
Ruby is a fun and powerful language. But that doesn’t mean we should go out of our way to be clever. We shouldn’t be eager to adopt quirky little idioms and erudite little stunts just because they are cute, or neat, or nifty. Code is hard enough to understand without having to think sideways through the next novel application of the ?: operator.
Professionals write clear and clean code. They use their language well. They use their language efficiently. But they don’t aspire to be master tricksters. Rather, they prove their professionalism by writing code that needs no explanation.
Comments
yachris 1 day later:
Three things:
You’re absolutely right about ‘clever’ code. I once read a Randall Schwartz article ostensibly introducing beginners to Perl… and having programmed Perl for several years at that point, I could not figure out what his code was doing without reading the article twice. It was that obtuse.
Your link to 8thlight.com is broken.
Do you think it entirely ethical to have a blurb on your son’s website with no disclaimer? Unless it’s some other Bob Martin…
Myles Byrne 1 day later:
Thanks for writing this up. I had been guilty of using the andkeyword to create one-liners as I always assumed && was the short-circuit form of and. As you’ve pointed out, they’re both short-circuit, and besides a subtle precedence difference are essentially aliases to each other.
The & operator (single ampersand) would execute both statements:
handle_batch(item) & display_batch(item) while items_remaining?
While the above is arguably just as tricky it does have the advantage of being familiar to anyone who has done some bash scripting.
Rimantas 2 days later:
What if the second action should be performed only if the first action returns not nil? And is very apropriate in this case. Like “find something and then display it”. If you find nothing you cannot display it, right?
I do not get your complain about ||= at all. I’d arugue that this is not tricky, no quirky and makes code easier to read once you are familiar with the idiom. With logic like this we should dismiss C altogether because it has pointers. Imagine, what you can do with that, oh my!. Dumbing everything down to the lowest common denominator is not smart nor clever.
unclebob 2 days later:
.bq Dumbing everything down to the lowest common denominator is not smart nor clever.
Agreed. However, using language features for unintended purposes is also suspect.
As I said, ||= has become a standard idiom, so I have no aversion to it, and use it myself. I just don’t like the way it became a standard idiom…
Andrew Walker 14 days later:
Great example. I have been bitten badly by ‘clever’ code over the years, some much worse than others. Each time it bites, it hurts because its you who looks bad when it takes a little while to fix something that should be ‘simple’/a ‘quick fix’.
Undoubtedly the ‘clever’ person who put it in place could fix it quite quickly, but how many times has that person conveniently moved on.
Many people in this business like being clever, and that is one of the toughest challenges. Its almost a prerequisite that this kind of lesson be learned the hard way – as in your example. Leave the offending code in place and teach/coach about the perils of employing clever techniques. Having said that, there is so much literature out there that advocates this kind of thing, you can’t necessarily blame people. Guess thats the reason I like to have experienced folk around.
Looking forward to your book.
Tony Morris 14 days later:
Side-effects and laziness do not mix. Welcome to programming language theory. Oh, and well done on reinventing monads ;)
http://sigfpe.blogspot.com/2006/08/you-could-have-invented-monads-and.html
agnoster 14 days later:
We shouldn’t eschew the use of idioms because some people may use them incorrectly. As was pointed out, that would pretty much rule out pointers. I use “A and B” (or “A && B”, depending on language) specifically because the short-circuit is the logical thing. If you’re not intending that behaviour, it’s not because you’re using a dangerous idiom – it’s because you’re using a perfectly natural idiom incorrectly. The fault, dear Brutus, lies not in the stars…
James Justin Harrell 14 days later:
It is a well established and perfectly acceptable practice to use ‘and’ operators in this manner when the short-circuiting behavior is desired. This is often used in shell scripts where you don’t want to execute certain commands if a previous command failed. This practice is far too old to be considered clever anymore.
Matt S Trout 15 days later:
And this is why perl provides the comma operator (ruby may, I didn’t check). Consider, for example
(warn(“foo\n”), warn(“bar\n”)) for 1..2;
at my Devel::REPL prompt this gives
foo bar foo bar
and the comma shows every indication of clearly being a separator; & is a bitwise operator with a return value you might care about, and as such doesn’t really express the intent of the code.
Charles 15 days later:
Well ruby doesn’t have the comma operator, but thankfully (unlike eg python) there is no statement / expression dichotomy. Ie:
a = [1, 2, 3, 4] until a.empty? p a.shift p '-' end
vs
a = [1, 2, 3, 4] (p a.shift; p a.length) until a.empty?
Ie we just use statement separators inside a sub expression rather than a comma operator.
Michael 15 days later:
I’m quite fond of the ‘and’ trick. I don’t think it’s clever at all. The short circuiting of ‘and’ is well known in most languages, and if that’s the behavior you want, it’s a concise way to get it.
In shell scripting and Perl, it’s a basic idiom used pretty much everywhere.
human i tarian 18 days later:
(Off topic) Hey Bob, now that the Iraqi premier has called for a timetable for the withdrawal of US troops from Iraq, how do you feel about your cheer-leading for the Iraq war of aggression? Changed your mind yet? Or are you still an obstinate old man refusing to let facts affect your arguments?
Christina Bacon 28 days later:
What, using an operator without understanding what it does can cause problems?! To the juniors defense, the [“tips”](http://www.rubyinside.com/21-ruby-tricks-902.html) site didn’t bother to explain what the operator does.
Piers Cawley 2 months later:
Personally I only use and and or for explicit flow of control reasons
And I tend not to use it so much in my Ruby practice anyway because it isn’t idiomatic in the same way that it is in, say, Perl.
The rationale is that I want to emphasize the happy path through a method and something like
unless filehandle = File.open('foo') raise "hell" end
breaks the flow of the method in ways I’m not happy about.
I don’t like ||= overly much either, though I use it where I’m certain that ‘false’ isn’t a legal value, but I come from Perl, where the community has been bitten by that one sufficiently often that more recent versions have added the// operator which checks for definedness rather truthiness.
filehandle = File.open('whatever') or raise "hell"
Daniel Brolund 3 months later:
I think your son did the right thing. In a way… :-)
Isn’t the real problem that computer science, in its use of boolean algebra, has hijacked the “and”-word (and “or” for that matter) to make it mean something that doesn’t make sense in a spoken language?
As an illustration, a programmer friend of mine answers questions like “Should we eat now or should we wait a while?” with a simple “Yes.”. :-)
Logically correct, but it really doesn’t help…
Tim Ottinger 3 months later:
My favorite saying is “one point of clear is worth two hundred points of clever”. It is clearly unwise to make code unclear, but in the passion for novelty that can be forgotten.
It’s even worse if a local hero (or worse, a distant hero) uses tricks. Surely any sufficiently advanced science is indistinguishable from magic, but if your code looks like magic then there’s something wrong.
Also note that cleverness is something that new programmers cherish and collect, but it smells mighty funny to us older guys.
God save us from being clever.
Tekme 5 months later:
If I remember correctly, Uncle Bob wrote in his Agile Software Development book that resposibilities should be put in thier own classes on a need-to basis (at least for the non-trivial cases). That is, you shouldn’t try to define the responsibilities of establishing a connection or forming your SQL until you find (the hard way) that your design suffers from the fact that the class has more than one reason to change.
Tekme 5 months later:
If I remember correctly, Uncle Bob wrote in his Agile Software Development book that resposibilities should be put in thier own classes on a need-to basis (at least for the non-trivial cases). That is, you shouldn’t try to define the responsibilities of establishing a connection or forming your SQL until you find (the hard way) that your design suffers from the fact that the class has more than one reason to change.
gothic prom dresses 8 months later:
Do you think it entirely ethical to have a blurb on your son’s website with no disclaimer? Unless it’s some other Bob Martin
acai berry benefits 8 months later:
If I remember correctly, Uncle Bob wrote in his Agile Software Development book that resposibilities should be put in thier own classes on a need-to basis (at least for the non-trivial cases). That is, you shouldn’t try to define the responsibilities of establishing a connection or forming your SQL until you find (the hard way) that your design suffers from the fact that the class has more than one reason to change.