All the things you can learn from a kata to become a better Programmer

Andrew Werner
6 min readJan 15, 2019

--

One of the easiest ways to get to grips with a new programming language is to run through a few code katas (for anyone who’s new to the concept a kata is a small learning/training exercise). Often the tendency is to put together a few tests, run through the challenge and move onto the next kata the instant those tests are passing but what if we delved a little deeper?

What i’ll be trying to get to is an understanding of how to create cleaner code (and also why we’d even want to do this) by going from the simple to the complex. Walking through a simple kata I’ll show how you can learn to spot some common patterns and then show how you can apply to a more real-world scenario.

Starting with a bad solution

Probably the most infamous kata is FizzBuzz. The spec for this is relatively simple and is usually some variant of the following:

Write a function that takes a number as an input and converts it to a string the contents of which depend on the factor of the number- if the number has 3 as a factor add "Fizz" to the output
- if the number has 5 as a factor add "Buzz" to the output
- if neither 3 nor 5 is a factor return the input as a string

Fantastic, that sounds easy enough we can probably code a solution to that without too much thought! Using JavaScript as a Lingua-franca:

function fn (number) {
let output = ''
if (number % 3 === 0) output += 'Fizz'
if (number % 5 === 0) output += 'Buzz'
return output || number.toString()
}

Awesome, we’ve just created a masterful solution! But is this really extensible? What if we add another requirement?

Changing Requirements

The problem we’re facing here crops up all the time. Someone has come along and pulled the rug out from under us: changing the original specification. So let’s mimic this by adding another requirement to our FizzBuzz program.

- if the number has 7 as a factor add "Bazz" to the output

You might be thinking, that’s annoying but still not really a problem: we just update our solution:

function fn (number) {
let output = ''
if (number % 3 === 0) output += 'Fizz'
if (number % 5 === 0) output += 'Buzz'
if (number % 7 === 0) output += 'Bazz'
return output || number.toString()
}

We’ve just copied and pasted a line of code, changed a number and a letter and now we’re good to go, not the biggest crime perhaps but something doesn’t feel right.

What would happen if we kept on getting asked to add new FizzBuzz features? Things could get tedious, the code could get hard to follow (the word spaghetti comes to mind). What if we put an operation out of order and created a bug? With our current approach and multiple future requirements it might take us a while to track down the offending line of code. Our productivity by this point is wilting! This is technical debt at its most simple: a small innocent change that on its own is harmless but when multiplied adds complexity and obfuscates the intended purpose of the application. Luckily greater minds that ours have already thought of a good way of avoiding this issue: by following the DRY principle (don’t repeat yourself).

Spotting the pattern

So how do we DRY out our code? How can we tackle this issue and make our solution more extensible and future-proof?

As a start there is one incredibly common pattern we can spot here. At the core of our solution we have data mixed into our operation. It’s the data that keeps changing but the operation is exactly the same each time.

Armed with this new realisation we can separate out the divisor and the string-result into a data structure and then create a general operation:

function fn (number) {
const data = [[3, 'Fizz'], [5, 'Buzz'], [7, 'Bazz']]
let output = ''
data.forEach(([divisor, word]) => {
if(number % divisor === 0) output += word
})
return output || number.toString()
}

This is looking better. We now have a generalised operation and data that it operates over. Could we take this even further? Certainly, we could pass the data as an argument to the function, allowing any data that conforms to this structure to be used . You might also be thinking that this is also a good example of the Single Responsibility Principle in that this function will now only be responsible for operating over data rather than defining and operating. Why is this good? Well, we’ve just increased its potential to be used in other situations with different data sets. Our kata is now more future proof!

function fn (n, data = [[3, 'Fizz'], [5, 'Buzz'], [7, 'Bazz']]) {
let output = ''
data.forEach(([divisor, word]) => {
if(n % divisor === 0) output += word
})
return output || n.toString()
}

Things get Functional

Now things are going to take a turn for the functional, because there’s another pattern at play here and again this is one that pops up all the time! I’m talking, of course, about … wait for it … Monoids!

Don’t let the term scare you, if you actually want to know about Monoids this blog is the best practical expalanation I’ve come across. However, for our purposes I’m going to ignore the terminology and maths completely. What we care about is spotting the pattern.

So essentially we have some data that shares a type: string (that’s our 'Fizz' 'Buzz' etc.), has some idea of what empty is: '' and lastly has a common operation that ties these pieces of data together: that’s our += or addition.

Ok, that’s not very complex. So what does this mean for us? Well Monoids are Incremental Combination friendly and there is a great core JavaScript function that’s just awesome for this: reduce. So how does this change our solution?

function fn (n, data = [[3, 'Fizz'], [5, 'Buzz'], [7, 'Bazz']]) {
return data.reduce((result, [divisor, word]) =>
n % divisor === 0 ? result + word : result, '')
|| n.toString()
}

Holy smokes, that’s looking a lot nicer!

Is any of this useful?

At this point we’ve hopefully learnt some new things, hooray for us! And ok, this works well in the context of our lovely, sheltered, kata-world but how is this useful in our day to day work? Well let’s expand our horizons and look at a simplified real world example.

We’re putting together a gorgeous website and so far have the following:

<!DOCTYPE html><html>
<head>
<title>Title</title>
</head>
<body>
<div id="homePage">
<h1>Welcome!</h1>
<script>
let page = document.getElementById('homePage')
if (new Date().getDay() === 1) {
page.insertAdjacentHTML('beforeend', '<p>Cheer up!</p>')
} else {
page.insertAdjacentHTML('beforeend', '<p>Hello</p>')
}
</script>
</div>
</body>
</html>

In the above example we assume that most of the time we want to have just the normal title and a paragraph but on a Monday everyone needs cheering up so we need to have a more cheery welcome message…do you see where we’re going? What if we get told that we need a different message for each day of the week, or even content some of which stays the same and some of which differs on any given day?

Well the exact same principles we’ve discussed previously apply. HTML can be treated in exactly the same way as our FizzBuzz strings: as a monoid. We can just define the different elements as data and then add them together into a single item.

<!DOCTYPE html><html>
<head>
<title>Title</title>
</head>
<body>
<div id="homePage">
<script>
const page = document.getElementById('homePage')
const data = [[1, '<p>Cheer up!</p>'][5, '<p>Friyay!</p>']]
const content = data.reduce((result, [n, message]) =>
new Date().getDay() === n ? result + message : result
, '<h1>Welcome!</h1>)
page.insertAdjacentHTML('beforeend', content)
</script>
</div>
</body>
</html>

We are left with a webpage that can be composed from a series of different elements based on random characteristics. For the sake of simplicity i’ll stop there but as food for thought: could you treat your predicate as data? Could you use the same pattern with say React or Vue components? Could you even use this pattern to build up your data…functions acting on functions as data acting on data…it might sound complicated but hopefully you’ve seen how as with our FizzBuzz example it can remarkably simple.

--

--