I was working on a Ruby thing the other day and found a bug in my code. I was trying to match when
blocks on multiple values in a case
statement, so I had written:
See the bug? I had intended to call do_something
if foo
was either "bar"
or "buz"
. But what I was actually doing was only checking if foo
was "bar"
, because "bar" || "buz"
is an expression that evaluates to the first non-nil literal, "bar"
. So the "buz"
alternative was never being checked. I should have been using a variable arguments splat-ish thing:
This bug was pretty devious; it flew under the radar for quite a while. I introduced it at least 5 years ago; it was there when I switched this project's repo from Subversion to Git in 2012, and since I didn't import history who knows how long it was present before that. A big takeaway is that I'm missing some tests. An interesting thing there is that to see that, I'd have to do not just line-oriented coverage checks but branch coverage. Not a great excuse but hey I'll take it.
What's funny about this bug is that the original code - although obviously wrong - feels right-ish to me. I've been trying to figure out why it looked right. Maybe that's mostly because it's syntactically valid. But I think it's also because it looks vaguely like the way things would work if the when
reserved word accepted a block and evaluated that block against the case
expression; something like when { "foo" || "bar" }
. Interestingly, when
does accept a block and yields the variable to it, so this would have worked:
That's fairly close to my original incorrect code. Not quite the same, and much clumsier than a varargs usage, but, there we have it.
So the takeaway is gosh, programming, am I right?