Favor Composition
Imagine you’re assembling an Ikea table.
You decide to break a leg knowing you’ll have to mend it back together before it can hold the table up.
Sounds crazy, right?
But we do this all the time when we’re coding.
Have a look at this example.
const length = (s: string): number => s.length
const stars = (n: number): string => [...new Array(n)].map(_ => '*').join('')
const mask = (unmasked: string) => {
const len = length(unmasked)
const masked = stars(len)
return masked
}
Can you see the broken leg in here?
We’re taking apart the mask
function only to put it back together
This might look contrived to you. Or you might not see anything wrong with it.
By the bottom of this page, I guarantee it will drive you nuts. And you will see it everywhere.
Here’s a slight improvement. (The big improvements come later in the
series.) I’ll call it maskBetter
and put it next to the old mask
for
comparison.
const length = (s: string): number => s.length
const stars = (n: number): string => [...new Array(n)].map(_ => '*').join('')
const mask = (unmasked: string) => {
const len = length(unmasked)
const masked = stars(len)
return masked
}
const maskBetter = (unmasked: string) => stars(length(unmasked))
It can be tempting to write this off. We know they mean the same thing, so who cares? Pick one. It doesn’t matter.
But that’s not true.
Look back at our metaphor.
The first problem with breaking a leg is that you’ve made more work for yourself.
We can see the parallel in the code by stating in simple words what a mask
is.
A mask for an unmasked string is a string of stars equal in length to the
unmasked string.
Look at the original definition of mask
and explain what it does.
First it takes the length of the given string.
Then it makes a string of stars equal to the length it got in the first step.
Then it returns the result of the second step.
That’s three sentences. And it’s accurate. And convoluted.
Let’s do the same for maskBetter
.
Make a string of stars equal in length to the original string.
This matches our description of what a mask
is exactly.
Back to the Ikea table.
Extra work isn’t the only problem with breaking the leg. You’ve opened up the possibility of putting it back together the wrong way.
What happens if you make this goofy mistake?
const length = (s: string): number => s.length
const stars = (n: number): string => [...new Array(n)].map(_ => '*').join('')
const maskWhoopsie = (unmasked: string) => {
const len = length(unmasked)
const masked = stars(len)
return unmasked
}
const maskBetter = (unmasked: string) => stars(length(unmasked))
Do you see the mistake in maskWhoopsie
? Do you see how that mistake is impossible to make
with maskBetter
?
It should be driving you nuts by now. If not, that’s ok. You won’t hurt my feelings. You can return to the slow imperative OOP(s) death march.
If it is driving you nuts, wait til you start seeing it everywhere. Then you can start fixing it wherever you see it. :)
And if you want ten more examples of this kind of code, put your email in below and I’ll send you another each day for ten days.
Each one will get more interesting, and more challenging. And each one will highlight a different problem solved by favoring composition.
By the time we’re done, there won’t be a broken leg you can’t stitch back together. And there won’t be a soul you can’t convince to stop taking table legs apart.