Archive for February, 2012
WAT
I’ve been spreading this excellent talk by Gary Bernhardt around my co-workers and friends who universally love it. I’m also proud to say the “WAT” meme has (hopefully permanently) entered my team’s culture as we find it adequately sums up our feelings towards various bits of software that we either have to work with or maintain.
Half my team is away on business trips or sick leave at the moment so it was a relatively quiet day, which the rest of us spent squashing bugs. Unfortunately for us, most of our codebase is in Ruby and we have started cultivating a reasonable collection of Javascript in some of our web-oriented interfaces so you can imagine that Gary’s talk was particularly poignant.
A co-worker had submitted a review request this afternoon (yes, code reviews are awesome) and I was talking a brief look through it as I was not that familiar with this piece of code. He’d admirably refactored some quite nasty stuff but I was a bit perplexed as to some of the logic. An example might be similar to this:
def check_vm_state(vm, vmstate)
if vmstate[vm][:host]
return vmstate[vm][:host]
end
raise InvalidHypervisorError
end
OK, it wasn’t nearly as bad as this but you get the point. We’re meant to pass in some kind of hash which contains status information, pull out something relevant to the VM in question and return it; if not, throw an error. What threw me was the test for this which was clearly just some fudged parameters but I couldn’t figure out what was going on:
assert_equals some_valid_value check_vm_state(1,2)
Again, for your sanity’s sake it was a little more than this. I wondered how this could possibly work, and my co-worker did the same. “But clearly the tests are passing!” someone exclaimed. Let’s take a look:
1.8.7 :001 > 2[1]
=> 1
1.8.7 :002 > 2[1][:host]
=> 0
WAT.
Apparently the [] operator on a Fixnum will retrieve the binary digit value at that index. OK, not too unreasonable. But what about the index off the :host symbol?!? Well, easy – it is cast to its unique identifier which is an integer value – in this case 16473 but this changes every time you run Ruby. This bit index clearly can’t exist for the number 2 so the return value is 0. WAT.
Here’s another cool one. We had some awful code that was a bunch of if statements testing for equality to various things – which in most instances you would just replace with a case/switch statement and be done with it. So we tried, and failed (initially):
> foo = 'hello'
=> "hello"
> case foo
> when 'hello'
> puts 'hello'
> when 'goodbye'
> puts 'goodbye'
> else
> puts 'something else'
> end
hello
=> nil
Absolutely no surprises there. Except in this case we’re more interested in the type of the object we are dealing with since our particular piece of code is making use of Ruby’s duck-typing to do some smart manipulation of various objects in similar ways. So now:
> foo.class
=> String
> case foo.class
> when String
> puts 'string'
> when Fixnum
> puts 'fixnum'
> else
> puts 'something else'
> end
something else
=> nil
Er… WAT? Obviously we failed to take into account that the case statement calls the === method on the operand in each when statement and it behaves completely differently depending on whether it is used with an Object or a Module (which Class inherits from as do String, Fixnum etc). For Module it will only return true if the thing being compared to is an instance or descendent of the thing being operated on, whereas Objects just compare equality (and not identity).
I’m sure this behaviour is also overridden in other types to “make sense” under the circumstances, but unfortunately just serves to confuse by its inconsistency. I realise this represents something like a raised middle finger to dyed-in-the-wool Rubyists but it really isn’t following any principle of least surprise that I know about.
Anyway, after all of us said WAT about nine-thousand times I came to the conclusion that duck-typing is only cool if not every single basic type in the language responds to most method calls, usually in completely different and unexpected ways. If it swims like a duck, looks like a duck and quacks like a duck, it could be a duck. Or some kind of shapeshifting, organism impersonating cyborg warrior from the future intent on the destruction of our minds and all we hold dear to us.
On a slightly less inflammatory note, Ruby is actually still quite nice to use once you know all it’s little quirks. I went to my first Clojure meetup in Berlin tonight and was introduced to some pretty awesome concepts. I’m not sure I’m completely sold but it may actually be time to broaden my horizons somewhat but I guess I’m stuck with Ruby for a while yet 😉
Archives
- February 2017
- January 2017
- December 2016
- October 2016
- August 2016
- June 2016
- May 2016
- August 2015
- June 2015
- April 2015
- March 2015
- January 2015
- December 2014
- November 2014
- August 2014
- May 2014
- April 2014
- March 2014
- February 2014
- January 2014
- December 2013
- November 2013
- October 2013
- September 2013
- July 2013
- June 2013
- March 2013
- February 2013
- January 2013
- December 2012
- November 2012
- October 2012
- September 2012
- August 2012
- July 2012
- June 2012
- May 2012
- April 2012
- March 2012
- February 2012
- December 2011
- November 2011
- October 2011
- September 2011
- August 2011
- July 2011
- June 2011
- May 2011
- April 2011
- March 2011
- February 2011
- January 2011
- November 2010
- October 2010
- September 2010