THE WRONG FIZZBUZZ
There are two common ways to implement FizzBuzz:
- if/else-if/else branching;
- string concatenation.
Both approaches can produce correct results, but one of them is fundamentally wrong. This is an article about its flaws — and a lesson which I’ve learned from them.
I’m not talking about FizzBuzz the interview technique. Any candidate who makes a working version— whether through string concatenation or if branching or whatever else — passes the test. However, the problems discussed in this article might be an interesting topic to cover in the interview process.
How to Look Stupid in Front of Your Colleagues
A function that is as simple as FizzBuzz should have an implementation that is as close to the requirements as possible. So, what does the spec say?
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”. (via Why Can’t Programmers.. Program? by Jeff Atwood)
Let’s try to implement exactly that. Our first attempt:
Even though this seems as close to spec as possible, this code simply doesn’t work. I myself made that mistake at some point while explaining FizzBuzz to a colleague (embarrassing!). The problem is, of course, that the else if (isMultiple(i, 3) && isMultiple(i, 5)) branch is never executed, since if either of those statements is true, one of the previous branches executes.
This is easy enough to fix:
But any bug is a symptom of unclear code. It points to a design problem. In this case: why does order of else-ifs even matter?
So, how can we make the design of this function clearer?
In Which We Unravel The True Essence of FizzBuzz
The bug appeared because we implemented spec word for word. Let’s take a step back and look at what the requirements seeks to convey.
The three variants — Fizz, Buzz, and FizzBuzz — are not equal. FizzBuzz is obviously constructed from Fizz and Buzz. And it should be printed only when both Fizz and Buzz would have been printed.
Obviously, FizzBuzz is a combination of Fizz and Buzz in precisely that order (it’s not BuzzFizz, after all). We never want to print FizzBuzz, we just want the combination of them. So the requirements can be reworded as:
Write a program that prints the numbers from 1 to 100. If the number is multiples of three or five, don’t print the number. Instead, print a string according to the following rules:
Start with an empty string.
If the number is a multiple of 3, add the characters “Fizz” to it
If the number is a multiple of 5, add the characters “Buzz” to it
This is, I think, the true logic of FizzBuzz. Let’s implement it, while avoiding code duplication:
But Will It Leak?
There’s a way to stress-test this implementation. If we really have revealed the logic of FizzBuzz, the implementation should hold well if the requirements change within that logic.
There’s a real-world variation of FizzBuzz called FizzBuzzBazz, which adds a Bazz for every number divisible by 7. In our implementation, it’s just a matter of adding 3 lines:
However, in the if-else implementation, this monstrosity would’ve been required:
Did we miss a combination? Is there an else-if statement out of order? I don’t know, and I’m not going to check. The problem that caused the initial bug has now made the code completely unmantainable.
This is the test of ever-changing requirements — and string concatenation approach handles it well, unlike the if-else approach.
Why This Is Really Important
Understanding underlying logic of requirements is a fundamental skill for a programmer, because of the challenge of changing requirements. The requirements always change, but they often do so within an implicit logic. If you see this logic, you can predict the change and make the essential architectural decision: where should the system be flexible?
Don’t implement the spec. Implement the logic behind the spec.
The best way to figure that logic out is to just ask. Most of the time, the answer is already there. Client, boss, user — any one of them may provide you with a clearer picture. Sometimes you just have to make the judgement on your own — and that’s a risk you should plan for.
The spec always serves as a guiding path in your research. At no point should the perceived logic contradict the spec (unless the spec is updated and approved by the client).
As programmers, we can take implicit logic of the requirements and make it explicit in code. The result is a more robust architecture and cleaner code. After all, as Robert C. Martin put it, “code is the ultimate expression of requirements”.