Introduction
FizzBuzz is a classic programming challenge for beginners or interviews (much like using factorials to demonstrate loops and recurision). The task is pretty straightforward. Usually it's something like
Print to the screen numbers from 1 to 100, but substitute numbers divisible by 3 with 'Fizz', those divisible by 5 with 'Buzz' and those divisible by both with 'FizzBuzz'.
A pretty straightforward way of doing this goes something like this. Consider the following markup:
<body>
<div id="container"></div>
</body>
Do a for
loop like this
const container = document.getElementById('container');
for (i = 1; i < 100; i++) {
const p = document.createElement('p')
if (i % 3 === 0 && i % 5 ===0) {
p.innerText = 'FizzBuzz';
} else if (i % 5 === 0) {
p.innerText = 'Fizz';
} else if (i % 3 === 0) {
p.innerText = 'Buzz';
} else {
p.innerText = i
}
container.appendChild(p)
}
But this code isn't very maintainable. Sure it solves the problem. But, what if someone needed to modify it by changing 'Buzz' to 'Baz' or the 3 to a 2? Or change the values around? What if it needed to be used over and over? This approach also mixes logic and item creation.
So, let's see if FizzBuzz can be made more flexible! Exciting right!? Right? Right.
Requirements
Consider the requirements of the problem for a moment. What needs to happen?
- Iterate over a set of numbers
- Test each of those numbers for its divibility against 2 other numbers
- Print a result based on that test to a page
- Select an element to attach the printed element to
- Select a type of element to print
It seems to me then that there are 4 parts of this problem that could be separated into different functions
- The
for
loop itself - The values (like 'Fizz' or 5) and elements (like the container) that need to be used.
- The logic for choosing which value should be printed
- Creating the markup
Configuration
If we're talking about functions, it would be nice to pass them arguments; and if we're talking about arguments, it would be nice to use an object that we could easily configure. So what if I start like this?
const config = {
fizzbuzz: {
fizz: {
val: 3,
message: 'Fizz'
},
buzz: {
val: 5,
message: 'Buzz'
},
},
elements: {
containerID: 'container',
itemType: 'p'
}
}
Next, I want the actual container element to attach to. This only needs to happen once though
const config = {
fizzbuzz: {
fizz: {
val: 3,
message: 'Fizz'
},
buzz: {
val: 5,
message: 'Buzz'
},
},
elements: {
containerID: 'container',
itemType: 'p'
}
}
const container = document.getElementById(config.elements.containerID);
// add the container to the config object
config.elements.container = container;
A For Loop and 2 Functions
We've already got an example to work with from the first for
loop. But I think it would be nice to have the loop execute a single function. It's going to need two arguments:
- The current number in the loop
- The config object
for (i = 1; i < 101; i++) {
createMarkup(i, config)
}
Create Markup function
The createMarkup
has just one job.
Put things on the page.
const createMarkup = (val, config) => {
// destructure the config object.
// there's no point passing the elements to the fizzbuzz logic
const { fizzbuzz, elements } = config
// create a new item of the type defined
const item = document.createElement(elements.itemType);
// set the text of the item to the value returned by the fizzbuzz function
item.innerText = fizzBuzz(val, fizzbuzz)
// place the item inside the container
elements.container.appendChild(item)
}
FizzBuzz function
Now, the fizzBuzz
function has only one job as well
Handle the logic.
const fizzBuzz = (val, config) => {
// destructure the config object
const { fizz, buzz } = config
// compare the value against the options and return a result
if (val % (fizz.val * buzz.val) === 0) {
return fizz.message + buzz.message;
} else if (val % fizz.val === 0) {
return fizz.message;
} else if (val % buzz.val === 0) {
return buzz.message
} else {
return val
}
}
Putting it together
Here's what the whole thing might look like
const config = {
fizzbuzz: {
fizz: {
val: 3,
message: 'Fizz'
},
buzz: {
val: 5,
message: 'Buzz'
},
},
elements: {
containerID: 'container',
itemType: 'p'
}
}
const container = document.getElementById(config.elements.containerID);
config.elements.container = container;
const fizzBuzz = (val, config) => {
const { fizz, buzz } = config
if (val % (fizz.val * buzz.val) === 0) {
return fizz.message + buzz.message;
} else if (val % fizz.val === 0) {
return fizz.message;
} else if (val % buzz.val === 0) {
return buzz.message
} else {
return val
}
}
const createMarkup = (val, config) => {
const { fizzbuzz, elements } = config
const item = document.createElement(elements.itemType);
item.innerText = fizzBuzz(val, fizzbuzz)
elements.container.appendChild(item)
}
for (i = 1; i < 101; i++) {
createMarkup(i, config)
}
What's the point?
How is this better!? It's nearly 5 times longer than the first solution! Wel... That's true. But, you could change how the whole thing works by changing the properties of the config
object.
It also separates the logic of which message should be used from printing adding that message to the page. This is useful since it makes the code easier to test. Values aren't printing? Well then it's probably an issue with the createMarkup
function. Wrong values being printed? Oh. Well that's probably a problem with the fizzBuzz
function.
Conclusion
Obviously this is kind of a silly exercise. But, I think the point is serious. We get to choose how we write code. After three years as a web developer, I've seen a lot of code (and probably written some) that solved the problem at the time and then was abandonded because a deadline was coming.
Sure, there are a lot of times when I'll start with the simplest thing I can think of just to get things going. Yet that doesn't mean that's where I stop thinking.