Reading Time: 5 minutes

“1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, …”

I said this so many times this year that by now, I could probably win a FizzBuzz-speed contest. But only for the numbers until 20 ?

Still, I was surprised how many people didn’t know about FizzBuzz as I thought this is one of the most common coding katas and often used in coding interviews. I find this one of the most versatile code snippets to talk about some basics in programming. Additionally, you can add a twist to it and get into more advanced topics. And this is what we will do today! We’re going to look into FizzBuzz and how design patterns can help us to structure our code better.

So grab a coffee and let’s talk about: FizzBuzz.

FizzBuzz

So for those who don’t know FizzBuzz: what is it? Originally, Fizz Buzz is a word game for kids. You probably played it or something similar in school (at least I did).

Have you found a pattern in the number/string sequence I wrote in the beginning? Some numbers seem to be replaced by either Fizz or Buzz or both of them (FizzBuzz):

  • Fizz is replacing 3, 6, 9, 12.
  • Buzz is only replacing 5 and 10.
  • FizzBuzz is replacing 15.
https://media.giphy.com/media/d3mlE7uhX8KFgEmY/source.gif

The FizzBuzz Challenge

Write a class FizzBuzz with a method play(int $length): array. The method is returning an array of numbers (as strings) but:

  • every number dividable by 3 will be replaced by ‘Fizz’
  • every number dividable by 5 will be replaced by ‘Buzz’
  • every number dividable by both 3 and 5 will be replaced by ‘FizzBuzz’

I created a small repository for this challenge that you can use and try it for yourself (don’t forget the unit tests ?):

https://github.com/moritzwachter/fizzbuzz-strategy/tree/first-challenge

First implementation

Are you done? Great! I will show you my solution using TDD. What did you say? We don’t need tests for this small piece of code? Maybe you’re right, but I want you to think of this challenge as an analogy for any kind of (more complex) business logic and changing requirements during the development process.

Before putting all the rules together, I start by implementing the rules one-by-one:

/**
 * @dataProvider isFizzDataProvider
 *
 * @param $input
 * @param $expectedIsFizz
 */
public function testIsFizz(int $input, bool $expectedIsFizz): void
{
    $actual = $this->fizzBuzz->isFizz($input);
    $this->assertSame($expectedIsFizz, $actual);
}

public function isFizzDataProvider(): array
{
    return [
        [0, false],
        [1, false],
        [2, false],
        [5, false],
        [11, false],
        [55, false],
        [3, true],
        [6, true],
        [15, true],
        [30, true],
        [33, true],
    ];
}
public function isFizz(int $input): bool
{
    return $input % 3 === 0;
}

Next, I did the same thing for the isBuzz rule.

Why did I even bother creating methods for those rules when FizzBuzz could be solved in a few lines of code? Moving your business decisions (i.e., conditionals) into separate methods makes:

  • it easier to test them in isolation
  • your code more readable
  • your code reusable as you might need those conditionals in another context again

Now there’s one thing which I always struggle with when doing the FizzBuzz challenge: Can you assume that the ‘FizzBuzz’-rule always is a combination of isFizz and isBuzz?

public function isFizzBuzz(int $input): bool 
{
    return $this->isFizz($input) && $this->isBuzz($input);
}

// vs.

public function isFizzBuzz(int $input): bool
{
    return $input % 15 === 0;
}

This is not a big deal when the business logic never changes. But if it does what could be the issues here?

Let’s say your client tells you that from now on every number dividable by 4 should be replaced by isFizz. In Scenario 1 isFizzBuzz would now replace 20 instead of 15. In Scenario 2, it stays the same – meaning if the rules are actually coupled, you need to do more work here. Because we can’t tell right now, we won’t get into coupling business logic with each other. In the end, when the business logic changes, our final FizzBuzz test for the play method should fail in any case.

I’ve shown by example how I tested and implemented the isFizz method above. In the same manner, I added the tests and methods for isBuzz and isFizzBuzz now.

Finally let’s put it all together and test the play method:

/**
 * @dataProvider playDataProvider
 *
 * @param int $limit
 * @param array $expectedResult
 */
public function testPlay(int $limit, array $expectedResult)
{
    $actualResult = $this->fizzBuzz->play($limit);

    $this->assertSame($expectedResult, $actualResult);
}

public function playDataProvider(): array
{
    return [
        'should return empty array' => [0, []],
        'should return first fizz' => [3, ['1', '2', 'Fizz']],
        'should return first buzz' => [5, ['1', '2', 'Fizz', '4', 'Buzz']],
        'should return first fizzbuzz' => [
            15,
            ['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', 'Fizz', '13', '14', 'FizzBuzz']
        ],

    ];
}
public function play(int $limit): array
{
    $result = [];

    for ($i = 1; $i <= $limit; $i++) {
        if ($this->isFizzBuzz($i)) {
            $result[] = 'FizzBuzz';
        } else if ($this->isFizz($i)) {
            $result[] = 'Fizz';
        } else if ($this->isBuzz($i)) {
            $result[] = 'Buzz';
        } else {
            $result[] = (string) $i;
        }
    }

    return $result;
}

Et voilĂ , 35 tests/assertions, and we tested everything (except for the render method). Now imagine: the client tells us that CLI output is just fine for this “feature” because it’s only used in some kind of CLI job. So you add a simple echo loop to render(), the ticket is finished, and everyone’s happy.

Two weeks later, the phone rings…

Your favorite client is calling you. They just had a meeting with another department which now needs the output of your feature, too. But they need it as a JSON array containing objects of the original input as a key and the output as value. I think every developer will experience this kind of situation often over the years. When is the right moment to make your code more flexible? Right now, we only have two different requirements for the output, so a simple if-else might be enough. But what happens if another requirement is added? Or even a fourth?

Now there’s a brilliant article about over-engineering and YAGNI (You Ain’t Gonna Need It) by Tom Dalling which I can absolutely recommend you to read:
https://www.tomdalling.com/blog/software-design/fizzbuzz-in-too-much-detail/

The twist

In our simulated situation, we assume that there will be many more output formats coming, and we need the flexibility inside our code. The ‘twist’ mentioned in the blog article’s title now is the implementation of a flexible output, which can create several output formats.

https://github.com/moritzwachter/fizzbuzz-strategy/tree/second-challenge

So feel free to take on the challenge and create your own implementation! Next week I will present how I implemented my solution and why ?

Moritz Wachter

Author Moritz Wachter

More posts by Moritz Wachter