Sometimes we can do a simple refactor and achieve a lot with it! The example that I'm gonna show was taken from a real project that has been working just fine for a long time.
Still, that doesn't mean we shouldn't take the initiative to improve simply because it's already working! However, we also need to be pragmatic and not fall into the perfectionism trap. Basically, we should find the sweet spot where the effort necessary is paid by its own results. 🕵️
I was working on a module that had a getMonth
function which would return the translation key according to the given month:
const getMonth = (month: string) => {
let translationKey = ''
switch (month) {
case 'January':
translationKey = 'JANUARY_TRANSLATION_KEY'
break
case 'February':
translationKey = 'FEBRUARY_TRANSLATION_KEY'
break
case 'March':
translationKey = 'MARCH_TRANSLATION_KEY'
break
case 'April':
translationKey = 'APRIL_TRANSLATION_KEY'
break
case 'May':
translationKey = 'MAY_TRANSLATION_KEY'
break
case 'June':
translationKey = 'JUNE_TRANSLATION_KEY'
break
case 'July':
translationKey = 'JULY_TRANSLATION_KEY'
break
case 'August':
translationKey = 'AUGUST_TRANSLATION_KEY'
break
case 'September':
translationKey = 'SEPTEMBER_TRANSLATION_KEY'
break
case 'October':
translationKey = 'OCTOBER_TRANSLATION_KEY'
break
case 'November':
translationKey = 'NOVEMBER_TRANSLATION_KEY'
break
case 'December':
translationKey = 'DECEMBER_TRANSLATION_KEY'
}
return translationKey
}
In this case, it was clear to me what I would accomplish using an object instead of a switch statement:
- readability
- cognitive complexity (you can read more about it here)
Why an object? Well, if you take a closer look at what the getMonth
function is doing, you realize that it's doing nothing but mapping keys to values, which is exactly what an object does! ✨
Therefore, a switch statement isn't needed at all. actually, it just makes the code less readable and increases its cognitive complexity. So, after refactoring:
type Month =
| 'January'
| 'February'
| 'March'
| 'April'
| 'May'
| 'June'
| 'July'
| 'August'
| 'September'
| 'October'
| 'November'
| 'December'
type Mapping = Record<Month, string>
const MONTH_TO_TRANSLATION_KEY: Mapping = {
January: 'JANUARY_TRANSLATION_KEY',
February: 'FEBRUARY_TRANSLATION_KEY',
March: 'MARCH_TRANSLATION_KEY',
April: 'APRIL_TRANSLATION_KEY',
May: 'MAY_TRANSLATION_KEY',
June: 'JUNE_TRANSLATION_KEY',
July: 'JULY_TRANSLATION_KEY',
August: 'AUGUST_TRANSLATION_KEY',
September: 'SEPTEMBER_TRANSLATION_KEY',
October: 'OCTOBER_TRANSLATION_KEY',
November: 'NOVEMBER_TRANSLATION_KEY',
December: 'DECEMBER_TRANSLATION_KEY',
}
const getMonth = (month: Month) => MONTH_TO_TRANSLATION_KEY[month]
I created a repository with both versions and used the cognitive-complexity-ts package to have numbers to compare (a higher score means a more complex code):
As expected, the usage of an object makes this code less complex and also more readable, since it's less verbose than the switch statement.
tl;dr: whenever we notice that the switch is doing nothing more than mapping keys to values, we should use an object instead 👌
Top comments (9)
Nice article. I'll just add that unless
MONTH_TO_TRANSLATION_KEY
is a dynamic object that could be changed at some point, you could simplify this by using thekeyof typeof
keywords to pull the object keys directly from the object, so you don't need to explicitly define the keys:It makes sense to add a
const
assertion:Given that object keys can only be strings or symbols (numbers are coerced to strings) Maps are useful as well:
Oh thanks a lot for the tip!
SpiderMonkey (FireFox) seems to be consistently favouring object property lookup (not surprising as that is a core mechanism in JavaScript that needs to be performant).
However somewhere around size > 1000 things get less predictable with V8 (Chromium). Perhaps putting
getObjectSwitch
on the hot code path may give it the full Turbofan treatment at some point in time.I think I tend to see your use-cases here, but apperately this might have been a bad choice for replacing it with a map methodology.
This example would definitely save you some congnitive effort understanding this piece of code by removing the variable and the late return statement.
Even if it isn’t just mapping you can also use methods in object to perform more logic per case.
I believe the "correct" solution is one step further, to use a map instead of a custom type of an object. It is effectively a set of key value pairs. But you could index it with whatever you like. The difference would be that the reader of code in the future easily can tell that a variable is intended to be a Map of key values whilst an object could be any object, stuffed with functions and what not. It makes more sense in a longer example or when variable naming is not as convenient.
Good article any how, the use case is spot on.
You could even stretch it to using functions as values in the map, would you need to do more things in any of the cases. Its still more readable than the switch 😊
Nice post 👍. This method has way more uses than mapping keys to values tho. We could make the object keys functions for infinite possibilities (if it is needed)
“Therefore, a switch statement isn't needed at all.”
A switch statement is more performant. But sure.