Ah, fizzbuzz, that venerable challenge of our industry. I've lost count of the number of times I've been asked to fizzbuzz in an interview. Seriously, I'd like to get a blindfold with "fizzbuzz" embroidered on it, so if I'm sent to the whiteboard I can cover my eyes and see if I'm yet able to Pin the Tail on the Fizzbuzz Donkey.
Some people dislike it. Some refuse to do it at interview. Not me. I've been on both sides of the interview table, and I've seen glistening CVs turn into candidates that can barely find the right side of the keyboard. So I'm fine with starting an interview with an idiot filter. We'll soon be on to talking about something more interesting.
But why am I writing about fizzbuzz? More importantly, why am I on holidaycation, sitting at a free bar, and writing about fizzbuzz? Because Someone Is Wrong On The Internet.
I saw a blog post today on the "right way" to TDD fizzbuzz in an interview, and it's got my goat.
I have mixed feelings about the value of TDD. I won't get into the gory details, because that's worth an essay in itself, but the particular aspect on my mind is that TDD has a tendency to lead you into spec blindness. That condition that every junior developer has to learn about, where you think that correctly fulfilling the spec leads to correct code (and a pat on the head). It does not. You graduate from junior developer when you start seeing the need behind the presenting problem1. And with fizzbuzz, the spec is deeply misleading.
The spec goes something like this:
So, Mr Wrong On The Internet advocates starting with this test case:
assertEquals("fizz",fizzbuzz(20).split(",")[3]);
Yes, this hits the spec. But it's testing too many things, and has tests of trivia mixed in with testing useful code. But the big problem is, if you start with "test the spec" as your guide then it's encouraging you to write the wrong code. It's the programming equivalent of treating the symptoms instead of going for the root cause.
Instead of string-munging through the symptoms, let's step back and do some quick analysis2 on this problem. Fizzbuzz has few parts, and almost all of them are crashingly dull:
Print stuff as a string
Yawn.
Generate a sequence from 0 to n.
Yawn. (range n)
, or your language's equivalent. Next.
Apply a transformation to a sequence.
Dull programming, but it does open the opportunity to pontificate about functional programming. I could probably even get the word 'Functor' in there, and that always gets the day moving. Still, dull programming.
Conditionally transform an integer.
There's the nugget. This is the /only/ interesting part of the challenge. Fizzbuzz can be written as:
(->> (range n)
(map f)
(str))
...where f
is the /only/ thing doing anything novel. Any coding you
do outside of defining f
is merely gluing the algorithm into the
core libraries. And so I put it to you, any testing you do outside of
testing f
is an integration test, and a crashingly dull one at
that. You might as well include writing tests for .toString()
.
So if you ever ask me to write fizzbuzz in an interview, I'll start by
writing a function of type Int -> String
. Actually I'll probably do
it in Clojure, because I think it comes out neatest:
(defn fizzbuzz
[n]
(match [(mod n 3) (mod n 5)]
[0 0] :fizzbuzz
[0 _] :fizz
[_ 0] :buzz
:else n))
Or maybe I'll go typecrazy with Haskell:
data Fizzbuzz
= Natural Int
| Fizz
| Buzz
| FizzBuzz
deriving (Show)
fizzbuzz :: Int -> Fizzbuzz
fizzbuzz n =
case (mod n 3,mod n 5) of
(0,0) -> FizzBuzz
(0,_) -> Fizz
(_,0) -> Buzz
_ -> Natural n
That's sweet. It changes the type and still keeps the untransformed integers available.
Someone will say I'm doing TDD wrong. So be it. I'm a heretic. I'm saying if TDD is, "translating the spec-as-given into a test and then writing passing code," then TDD is wrong3. However I solve fizzbuzz, you won't see me start with string-slicing tests4.