Fizz Buzz Challenge, or How Much Can We Complicate Three Lines of Code?
1. Glorious Beginning
OK. This is officially my first post on my new site. And I will start it with FizzBuzz algorithm.
The idea is very simple: if the number is divisible by 3, return “Fizz”. By 5 – return “Buzz”. But if it is divisible by 3 and 5, then return “FizzBuzz”. How much more simple can this be?
This algorithm can be an assignment during job interview. However, in this article not only I will try pushing this simple code closer to the limits of Object Oriented Design, but I will also tell you that this original implementation totally stinks. I will tell you why and how we can fix it. So, let’s do some really crazy, insane algorithmics together!
Please note
You can click on the pictures to make them larger
2. Pleasing Results
This is the output. As you can see, numbers divisible by 3, 5 or both, were marked accordingly. Then what’s the problem? you may ask.
WARNING!
In real life, requirements can and will change!
Yes. And business users may come with new updated requirements within a week or two after the project was deployed into production. Now what?
Well, let’s try to take our FizzBuzzing to somewhat professional level. Shall we?
3. Problem Is About To Happen!
And it usually happens in a form of a friendly request to modify the application to meet new requirement(s). Nothing too crazy.
In this case, let’s say we need to add another check to our FizzBuzz application. Since it was printing “No luck today!”, we need to add division by 7 and print “Lucky Seven!”. That’s positive, right? And we are happy to add another check. Just two simple lines. And this is how many applications have been done!
DANGER!
We have just destroyed, completely, "Open-Closed Principle" in "SOLID", the foundation of Object-Oriented Programming!
4. Right Path... To The Failure!
As soon as you made this tiny changes, not worthy even a short conversation, the business passed a new requirement: “Lucky Seven!” should only happen on Sundays. Easy, right? You know where it is in code, and you go an change it to accept a “day_of_the_week” variable. This may only take a few minutes…
Yes, stop! Right now! Read the text above again. Do you realize that OOP does not exist in this code, and even if you make the second variable optional, there's a big risk of introducing a bug into the code!? Especially if it is used in more than one place.
5. Cooking Spaghetti
I don’t know if you realized it or not, but we have just started cooking a nice bowl of “spaghetti” code. But wait! The business has a couple of other requirements, and we need to implement them. Here we go!
- FizzBuzz, or division by 15, should only happen on Saturdays and Sundays
- In case of FizzBuzz on week days, on Fridays it should be handled by 5, and other days – by 3
- Lucky Seven should only happen on Sundays, but in case of 105 on Sunday – which is 3*5*7, FizzBuzz should take a precedence.
- The rest of operations should run as usual
This is when you start planning to move to another project. Well, almost...
6. Taking a Moment to Reflect
Let’s take a look at the code above. What problems can you see? For the sake of this article, I will only point at one.
WARNING!
Mixing data with logic is a "big, big mistake. Huge!"
What I mean by that is bringing hardcoded data, such as strings ‘saturday’, ‘sunday’ or numeric values 3, 5, 7, 15, into logic, which in our case is a whole bunch of “if” statements. To solve this, I will try to do two things:
- Take data out of logic (or take logic away from data, depends on how you look at that)
- Get rid of hard-coded block of “if” statements
7. The Strategy of Cleaning My Code
Well, the “Strategy” is the name of well-known OO design pattern. So the title of this section is play on words. But before we go into Strategy, let’s look at the code above, section 5, again. What are we trying to accomplish? Here’s what we have:
- A set of nested “if” statements
- Specific values used by IF statements to decide what will be executed
- Executable code, which in our case is simply returning a string we will print later
What you see in this section, is four separate objects, functions. I basically split the large “IF” block. Each function has “if” statement inside, guardian clause which is checking if the argument is what this function is working with. This is called “Fail fast principle”. However, now we have a problem.
In order to determine which algorithm to call, we need to figure out what type of number is coming. Meaning, is it divisible by 3, 5, 7, or combination of those. HOWEVER (and here you may read twice) determining the divisibility of the number is EXACTLY that these functions are doing!
How can I determine which function is needed if determining which function is needed is inside the function itself? OK, I promised to do something insane, and I will. Keep reading. In a meantime, here’s a short note.
Just in case you didn't know and wonder why I put argument as (value: int), Python allows specifying arguments and return types. Keep in mind, this makes absolutely no difference for Python. But it is used to document "ins and outs" for each function. More on this later.
8. "Strategizing" My Application
So, genius mind of GeneUs developer came up with this amazingly simple solution:
- Create a package folder called “fizz_buzz_validators”, call each strategy object “validator” and put it there
- Import validators and add them to the list of validators
- Names of validators, such as “val_03”, “val_02”, etc., determine their order
- Iterate over the list for each number. Because we have “fail fast” clause at the beginning, each validator will return either a valid result or a False boolean value which we will use
Never use this approach. It stinks! Do you see the problem?
9. Let the Quest for More Flexible Solution Begin!
Hope you saw the issue with the code in section 8. Even though I put strategies in separate objects, which is good, I’m still importing them manually. And that creates very tight coupling between all those pieces. I have accomplished nothing!
First, I will take hardcoded objects names out of my code. I mean completely! Lines 7-13 show hard-coded values in this example. However in reality I would bring them from configuration file. Then lines 15-16 use values from the list of validators to import specified objects dynamically! My code doesn’t care about hardcoded values any more! Nice!
Can you identify a huge problem with this code?
10. Asking Factory Working Class for Help
The code in part 9 has two major issues.
- There’s no separation of concerns. The same is importing and validating (lines 16 – 17)
- And this is a big one! The code is initializing each object for each number! What’s a problem? For just one number, there’s none. What if we feed 100,000 numbers to check? Or more?
The factory I designed is solving these issues. It is taking the task creating list of validators out of main code, and it is only doing it once per application run. Still, “_validators” will come from outside.
I have no doubt you noticed list comprehension I am using in "validators" property of the class. But can you tell at first glance what are the elements of the list?
11. Factory Version 2.0
I thought my coding insanity would not be complete without introducing this approach. I am not sure I’d use it for this application, but it sure has time and place.
In this version I use os.listdir(path)Â to get a list of files in specified folder. I set default value to ‘fizz_buzz_validators’ for the sake of this demo only. This means I don’t even need a list of validators. As long as the object is in specified folder, this Factory will pick it up! Awesome! Validators have to start with “val” and have .py extension to be considered by my Factory. This will eliminate other files, like __init__.py.
I am also using “typing” package here to specify “-> List[str]” and “-> List[Callable[int], str]]”. Just like before, this means nothing for Python, but it helps me to remember, for example, that property “validators” returns a list of callable objects, functions, which take ‘int’ as input and returns ‘str’ as output.
Same name "_validators_list" for method and attribute for no reason. I noticed later. But it worked.
12. Our Factory Class At Work
Now is a good time to put our factory to work. Let’s look at the code.
I am importing my factory class. Yes, hardcoded for this demo. Using dependency injection is a little bit outside of the scope of this article. So direct import will do for now.
Then I am creating an instance of the factory, return a list of validators (line 14). And then looping to get through the numbers, just like a good FizzBuzz should’ve done to begin with. Except now we can scale our FizzBuzz up to MegaFizzBuzz with very little modifications.
Let’s take a look how it works. But don’t go too far. We have a curveball coming, I can see that…
13. Let's FizzBuzz For A Change!
Now it is a good time to enjoy the results of out Object Oriented FizzBuzz. It is definitely working!
Do you see the problem in this result?
First of all, look at Number 21. It is Fizz. Of course, I implemented the original algorithm here, where division by 7 is checked after division by 3. In section 8 I showed you the order of my validators, and it was based on the file name (val_01…, val_02…, etc.) That is a big problem!
If you remember what I wrote in Section 4 and especially Section 5, you know that requirements can and will change no matter how perfect the code is. And this code does not offer much flexibility. Yet. Even after all objects have been disconnected, nothing is replacing the “if” selection algorithm we had in the original algorithm. And that means if requirements change, we have no way to implement them.
So my admiration for my design had only lasted until Number 21. That kind of ruined it, but better now than later. Let’s fix it!
For the sake of simplicity, I will only implement one requirement: division by 7 should take precedency over division by 3 or 5 on Sundays.
14. Rules Object Rules!
“Rules” is another OOP Pattern used to separate logic rules from the business logic. I will implement it here, and this will be the last design pattern for this article.
I will implement the requirements from Section 4 – Division by 7 should only happen on Sundays before 3 and 5. And the block with “if” statements did nothing except defining the sequence of pieces of code. Those that I split into separate validator objects.
So instead of running “if” statements, I will simply create a sequence I need. “IF” logic was checking the day of the week, and if it was Sunday, then division by 7 was checked. Easy. And now I will not even need day_of_the_week into my code. I just need an object which defines that sequence. And another module will supply the “rules” to my factory. Here I did it in JSON file, but you can do it any way you want.
15. The Ruler Of Rules
The “Rules” pattern can use classes as rules objects. But in this case I thought I could use JSON file. And now I need a Rule class, an object that will get the rule and bring it to me in consumable form. Kind of like “Factory” for the rules.
It is taking the name of the rule_file, and its property will give us a list of validators to bring into process. Not only this allows us to change the sequence of rules, we can define as many rules as we need, and we only add to the sequence those we need for the particular process.
16. A Good Foundation For The Ruler
Since I started putting rules and validators here, I think you may need to look into the “L” of SOLID. It stands for Liskov Substitution Principle.
I know, I know, Python does not have concept of interfaces, like C#, for example. Neither does C++. However, both Python and C++ have “Abstract Base Class”, and that’s what I would use in this case. The package “abs” (yes, it stands for “abstract base class” is exactly what I will be using here to put it to practice. Just in case I would need more Rule Factories in the future. Or maybe I am just adding it for fun, I let you decide.
In these two pieces of code you see the Rules_Base class with @abstractproperty, and you see Rule class implementing this base class. This Rule object is what I will pass into my Factory to get validators.
17. A Functional Factory At Last!
Finally I have a Factory which I most likely leave alone. Here’s what we have so far:
- The Factory is a class which expects another rules class implementing Rule_Base abstract class. Like I wrote a couple of times here, Rules_Base annotation is just a hint for developers, not a type check.
- Variable ‘path’ is optional here. I put it for the sake of this demo. In reality I would most likely create a class composition, an object containing all necessary info for the Factory. Including rules objects as one of its attributes
- Factory does not care about rules and sequences. It delegates this task to rules object, and its only job is to take a list of validators and return a list of objects those names represent
18. Lo And Behold! We Are Back To Original Code!
Not the original code from Section 1, but we are back inside the main module of the application.
- The rule, in this case ‘rule_lucky_sunday.json’ should be supplied by another dispatcher class. This should not be a concern of our application. Nor do we need to know what day of the week it is. Just give us the rule to follow today.
- On line 15 we pass the rule name into Rule object r(str), which becomes an argument for validators Factory val(Rule_Base).
- The property .validators of the Factory returns a list of validators
- Now we can iterate over validators and call .validate(int) function to determine if the number fits the criteria. The first valid response will return the value.
19. The Final Result
Finally, I got the result I was looking for. Number 21 is “Lucky Seven!” This happened because I used the rule “rule_lucky_sunday”. If I need another rule, I would need to pass it as an argument into the Rule object. While you are taking a moment to admire my work, I will mention several items I would need to add/modify in this application.
- I am not checking if the arguments are valid. This is called “fail fast”. I won’t go in real code without that.
- I am a fan of defensive programming style, so propagating exceptions is not acceptable. All possible exceptions have to be contained within the scope where they happen.With no exceptions! (Yes, pun intended)
- Adding configuration file for those string variables, possible dependency injection. For production code I would need to do that.
- Most likely, if I follow OOP style, I would favor classes as opposed to just functions. But I also like using functional approach, so I like and I will use functions as arguments or return types.
This obviously was not a code for production, but I think it can give a pretty good idea about some programming techniques that can easily turn such a nightmare application, like FizzBuzz, into a state-of-the-art Object Oriented Design!
20. The Icing On The Cake
Here’s the overview of the workflow in the final version of my application
- FizzBuzz module is called by another process. Whatever that process is, it needs to supply the name of the “rule” object FizzBuzz needs to follow at each specific execution. It does not care about dates, time or any other outside circumstances. Juts the name of the “rule”.
- FizzBuzz calls the Rule dispatcher (or Rule Factory) with the name of the rule.
- It receives the concrete implementation of the rule. In case of this application, the rule looks like a list of validation objects we need to use in a specific sequence to validate the number.
- FizzBuzz is sends rule object (a sequence) into a Factory
- Factory uses a list of validators to look for objects
- Factory imports those objects into the process, and it creates a list of objets according to the rule object
- FizzBuzz receives a list of validators as a response from Factory
- Finally, FizzBuzz takes the numbers and validates each one against the list of validators. The answer will be sent to the initial caller, and in our case just printed to the console (or terminal) window.
Epilogue
So, did I pass my interview assignment? Thanks for taking your time to look at my article. I hope you find it crazy enough. You see, with certain persistence we can use OOP even in such an ugly piece of code as FizzBuzz.
Well, all jokes aside, I’ve seen too much FizzBuzz in real production code. I mean “if”-branching, often nested, spaghetti code which is easier to rewrite from scratch than refactor to implement new requirements. Object-Oriented Programming does require putting in place some coding practices. But with some time investment upfront, in a long run modifying your application becomes as easy as taking one USB cord out and plugging another one in. In this article I briefly demonstrated Factory and Rule OOP patterns and mentioned S, O and L of SOLID (Single responsibility, open-closed and Liskov substitution).
The final application is not what I push into production without some serious error-proving and modifications. But I think it can be a good start in case somebody wants to use a real life FizzBuzz algorithm.
Thanks for reading.
- Say Hi to me on LinkedIn, let’s connect!
- Stay tuned. I plan to keep posting about algorithms, Object Oriented Design, DDD, and many other things, both in Python and C#
- Check out my Course about Single Page Application using ASP.NET MVC Core and AJAX, if you use C#
- And the most important, make a wonderful day happen today!
Sincerely,
Gene