One antipattern I see often is the magic number.
The magic number antipattern is a direct usage of a number in a function. Here is an example that comes from Refactoring by Martin Fowler.
function potentialEnergy(mass, height) {
return mass * 9.81 * height;
}
What the hell is 9.81? Yeah, I don’t know either. Maybe you do know what 9.81
represents in this context, but what about the junior engineers on your team that aren’t as familiar with scientific formulas? One goal of a senior engineer is to write code that is easy for junior engineers to understand. There is an easy way to change functions that have a magic number to make them more readable.
Here’s a refactored example of the same function:
const STANDARD_GRAVITY = 9.81;
function potentialEnergy(mass, height) {
return mass * STANDARD_GRAVITY * height;
}
This refactor has a specific name. It is called “Replace magic number with symbolic constant” and it is the refactor that eliminates magic number antipattern. If you compare the two code samples, all we did in example two was place the number 9.81
into a constant that is well named and easily understood by any developer that comes across this code later.
Why is magic number bad?
1.) It can be hard for future engineers to deduce quickly what the number is referring to. This is something you might think is reasonably deduced, but it’s difficult for you to tell because you a lot of context when building a feature. The next developer won’t know as much about this feature as you do right now. You want another engineer to glance at your method and understand what is happening quickly. With magic numbers, this isn’t always easy.
2.) You are probably violating the “Don’t Repeat Yourself” Principle. Many of these numbers will probably be reused in your application and should be stored in a constants file. If you have 8 instances of using the MINIMUM_PASSWORD_VALUE
, you need to change it in one location and all the logic is the same in your application. If that instead, is the integer four floating around in 8 different methods, it may be hard to decipher which 4
is referring to the minimum character count of a password and which 4
is talking about other unrelated logic in your application. When you repeat code, problems occur when making changes – the magic number code smell makes that an even larger issue.
3.) Protection against typos. Even for numerical value that won’t change, it’s easy to slip typos into your code. For example, it’s not too difficult to accidentally type 3500 instead of 3600 when referring to seconds in an hour. It’s an easy mistake to make. The ramifications might be different depending on your application. A few years ago, I made the mistake of adding an extra 0 to a value. A 10% discount soon became a 100% discount for a surprised a delighted customer. My product manager mentioned this to me and I quickly made a code change before too many happy customers starting receiving free services from us.
Here’s another example of the magic number in action
setTimeout(function () {
alert("doesn't work - instant alert");
}, 30 * 24 * 3600 * 1000) // 30 days (more than 2^31-1 milliseconds)
The above code snippet isn’t the terrible because it explains what the math is calculating. If I was reviewing this code, I would probably hit the approve button, but then ask “Why do you need this comment here when you can put this logic in a variable and name the variable what the comment says?”. I would recommend something like the following:
THIRTY_DAYS_IN_MILLISECONDS = 30 * 24 * 3600 * 1000;
setTimeout(function () {
alert("doesn't work - instant alert");
}, THIRTY_DAYS_IN_MILLISECONDS)
Positives of this new code is that you removed a comment that would very likely not be changed when new code changes. So that’s a big plus. But we are left with the magic number in the formula for building the constant we end up using. If you keep going down the rabbit hole, you’ll end up with something like this…
const MILLISECONDS_IN_A_SECOND = 1000;
const HOURS_IN_A_DAY = 24;
const SECONDS_IN_AN_HOUR = 3600;
const THIRTY_DAYS_IN_MILLISECONDS = 30 * HOUR_IN_A_DAY * SECONDS_IN_AN_HOUR * MILLISECONDS_IN_A_SECOND;
setTimeout(function () { alert("doesn't work - instant alert"); }, THIRTY_DAYS_IN_MILLISECONDS)
That feels a little cleaner and easier to understand. In an actual program, you would probably use a constants.js
file to a lot of the reused constants in your application.
Dates and times might not be the most impressive use of the fixing the magic number code smell because it is so universal. The real power of magic numbers come from making sure you have constants related to business rules. This becomes more clear as we move into our next example.
Ecommerce checkout logic is an area where you might encounter the magic number code smell. In these calculations, you have all types of numbers and if you don’t name them and put them in constants properly, the logic can get out of hand quickly. Take a look at the following function.
function getTotal(subtotal) {
subtotal = subtotal + (subtotal * 0.07);
if(subtotal < 50.00){
subtotal += 20;
}
return subtotal;
}
It is somewhat hard to follow on a glance. You can probably figure most of it out. But even if you did, you would be missing some valuable context. Here’s a refactored version of this code that makes it much more readable and provides more context.
function getTotal(subtotal) {
const NEW_JERSEY_SALES_TAX = 0.07;
const FREE_DELIVERY_MINIMUM_PRICE = 50.00;
const SHIPPING_COST = 20.00;
subtotal = subtotal + (subtotal * NEW_JERSEY_SALES_TAX);
if(subtotal < FREE_DELIVERY_MINIMUM_PRICE){
subtotal += SHIPPING_COST;
}
return subtotal;
}
Even if you were able to deduce that we were checking for a minimum delivery fee and adding tax, you might not have known that this was using New Jersey sales tax. This would have become important if we found out that the New Jersey sales tax had changed at some point in the future.
The next time you write a piece of code and open up a pull request, take a look if there’s any numerical logic without context. Maybe you might write some code tomorrow that can easily be made more readable by replacing a magic number with a symbolic constant.