DEV Community

loading...
Cover image for Stop waving the wand of magic numbers

Stop waving the wand of magic numbers

edA‑qa mort‑ora‑y
I'm a creative programmer and puzzles designer. I cook monsters.
Originally published at mortoray.com ・4 min read

37. You have no idea what that number is, do you? A number without context nor a label is a random value. It doesn't tell us anything. Imagine walking by a billboard, with a picture of a person on a boat, and the text is a giant number 89. I'd be intrigued, but utterly confused. We rightfully reject meaningless numbers...

...so why then do they appear so often in source code? Any time a constant appears in an expression, like 12, we call it a magic number. Through some dark craft, they arise out of the ether and populate our code. We don't know where they came from, what they mean, or how they got there.

Though I don't go down to the level of this article, my book has a chapter about interviewing as an essential programmer skill. Something simple like fixing magic numbers can leave a positive impression on the interviewer.

An example of magic

When I conduct interviews, I ask the candidate to create a deck of cards. I let them simplify it, by using sequentially numbered cards, instead of suits and ranks. In the majority of code I'm presented a loop of this form:

for i in 0..52:
    cards.add( i )
Enter fullscreen mode Exit fullscreen mode

The number 52 doesn't convey any information. Only because we're talking about a card game, and the candidate has assumed a standard poker deck, does the number 52 appear. There are of course many card games that don't have 52 cards.

I request they deal the cards out to multiple players. I accept splitting the deck as well. Frequently, I get code like this:

for i in 0..26:
    player1.add( cards[i] )
for i in 26..52:
    player2.add( cards[i] )
Enter fullscreen mode Exit fullscreen mode

It's not immediately obvious that this is splitting the deck in half. I have to reconstruct that in my head. Making a mistake while writing is easy. What if instead, you saw this code:

for i in 0..27:
    player1.add( cards[i] )
for i in 28..51:
    player2.add( cards[i] )
Enter fullscreen mode Exit fullscreen mode

Is the code accounting for some inclusive/exclusive end condition? Is there a bunch of off-by-one errors in there? What is 51? It doesn't line up with any number I know at all, not even about cards.

Magic number

In this code:

for i in 0...52:
Enter fullscreen mode Exit fullscreen mode

The 52 is known as a magic number. A future coder has no information about what it is. These types of numbers convey no information as to their purpose.

It's easy to get rid of them. Give the numbers a name.

num_cards_in_deck = 52
for i in 0..num_cards_in_deck:
Enter fullscreen mode Exit fullscreen mode

We've immediately improved the quality of this code. A reader knows what the number 52 means. The loop now logically does something for every card in a deck.

Giving a name to a number removes its magical quality. Here we've done it beside the code, but typically symbols like num_cards_in_deck end up as global constants. They are context-free facts. If multiple pieces of code need the same value, then create one constant and share it.

Another response to the dilemma is to add a comment, such as # create a standard size deck to the loop. Just say no to this! Comments are a tool of last resort. They have no structure, can't be enforced by the compiler, and inevitably become out-dated. Using proper symbol names is superior -- code trumps prose.

With our new constant, we can change the second loops:

for i in 0..(num_cards_in_deck/2):
    player1.add( cards[i] )
for i in (num_cards_in_deck/2)..num_cards_in_deck:
    player2.add( cards[i] )
Enter fullscreen mode Exit fullscreen mode

Again, the quality of the code has improved considerably. These loops now have meaning; it's easy to see we're treating the deck as two halves. It's also much less likely to have an error as we're letting the compiler do the division. Moreover, if the number of cards changes, this loop is still correct.

I begrudgingly use this example only because it comes up in my interviews. These two loops are still bad. From a defensive programming standpoint, you shouldn't use a constant value at all. You should use len(cards) and len(cards)/2. Many magic numbers can be removed in this fashion. Rather than tying your code to an arbitrary value, base it on some other knowledge you already have. In this case, we have a cards collection which has all the information we need.

Put the wand down

Not all constants are magic numbers though. There are some special cases, in particular, 1. Adding 1 to a number creates its natural successor. There'd be no confusion about what is happening. There are more, but without seeing the code in particular, I don't wish to give general exceptions.

Even a number like 2 may not seem magical, but can nonetheless be improved upon. In the example I've given, it's clear that it splits something in half, but it doesn't convey what half means. It'd be better to say num_players in that example. Many times, even if the number is obvious, it helps to give it a name. They add clarity to the code.

As fun as magic may be, it's time to put the want down and stop conjuring these numbers into our code.


Read my book What is Programming? and learn what it takes to be a great programmer. I look at the People, the reason software exists, the code at the heart of that software, and you, the person behind the keyboard.


Addendum: A better dealing loop

The dealing loops shown above are still wrong in my opinion. Though not related to magic numbers anymore, here is code that does the dealing between players in a cleaner fashion. It avoids duplicate loops.

for i in len(cards):
    player_cards[i % num_players].add( cards[i] )
Enter fullscreen mode Exit fullscreen mode

Another possible option, which closer mimics dealing, and avoids ranges entirely. It also uses an OOP player that contains cards. This type of code is suitable when you need to model all the states visually, or when there is partial dealing involved. It retains intermediate states.

cur_player = 0
while len(cards) != 0:
    player[cur_player].cards.add( cards.pop() )
    cur_player = (cur_player + 1) % num_players
Enter fullscreen mode Exit fullscreen mode

Discussion (25)

Collapse
drbearhands profile image
DrBearhands

Story from a friend.

Code was outsourced offshore, because that would save money (it didn't). The company had a rule, and tooling I expect, against magic numbers. So the creative little buggers did this:

int seven = 7;

[...]

for (int ii = 0; ii < seven; ii++)
Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author

That's not creative, that's "clever".

I frown on clever coding.

I wonder if other variables were named iii, iv, v, etc?

Collapse
drbearhands profile image
DrBearhands

the ii name was my own addition. I was under the impression this was fairly common for indexed array iteration.

Thread Thread
mortoray profile image
edA‑qa mort‑ora‑y Author

I don't think I've seen it before. I might have blocked out the memory. If I need a generic variable I just use i, or j, but I prefer giving them better names when possible, like row, or x, or item_ndx.

Thread Thread
drbearhands profile image
DrBearhands

Oh, ii is often used rather than i for iterators because it's easier to text search. Then again if you have to search for the iterator you might be doing something wrong.

Thread Thread
mortoray profile image
edA‑qa mort‑ora‑y Author

I don't think I've ever searched for an iterator variable.

Collapse
jenc profile image
jen chan

Is that ... a spread operator?

Thread Thread
drbearhands profile image
DrBearhands

nah, [...] is often used to indicate that some part was cut from a quote. I guess it doesn't translate well to code blocks.

Thread Thread
mortoray profile image
edA‑qa mort‑ora‑y Author

I often do three vertical dots for this:

trailing_code
.
.
.
next_important_code
Collapse
itr13 profile image
Mikael Klages

If a magic number is hard to name, then a comment explaining how it works might be needed too.

And if there's a true magic number that you don't know why works, then you probably shouldn't have it at all.

Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author

I'm okay with a comment on the constant, but not where it is used. Sometimes numbers come from external calculations that are not done in the code. This is common for many formulas in math, science, finance, and graphics.

I've had comments before referring to the code used to calculate the constant. I did this when it was too costly to calculate each time.

Collapse
itr13 profile image
Mikael Klages

One feature I can't wait for more languages to implement, is compile time code execution. Then such costly functions could be visibly coded, but also not have to run each time the program is opened.

Thread Thread
mortoray profile image
edA‑qa mort‑ora‑y Author

Does anything other than C++ offer this now?

I've usually resorted to a pre-build step that modifies source files.

Thread Thread
itr13 profile image
Mikael Klages

I think I read that scala has it? Personally I'm pretty excited for JAI (though that might not be released for a few years), but that may be mostly suited for game-development rather than general programming and software.

Collapse
craigmc08 profile image
Craig McIlwrath

Whenever I hear magic number I always think of the quake fast inverse square root. en.m.wikipedia.org/wiki/Fast_inver...

Collapse
oscherler profile image
Olivier “Ölbaum” Scherler

That was an awesome read.

Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author

For square root fun, also see my article Calculating square root using Newton’s iterative method

Collapse
gmartigny profile image
Guillaume Martigny

To read more on the subject, you could to the eslint page about magic numbers.
It's written for Javascript, but you should consider this rule for any language.

Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author

I'm not so happy with their detectObjects rule. I like grouping my constants into objects, but they seem to encourage making them all static globals. I guess it's configurable though.

Collapse
gmartigny profile image
Guillaume Martigny

The default value for "detectObjects" seems to be false, meaning it will allow you to put magic numbers inside objects (like you do). You don't have to activate the rule, and I think constants under "enums" is a great thing.

Collapse
jenc profile image
jen chan • Edited

Omg! Second time coming across a counting cards question...
And started thinking I ought to know card games.

I think the takeaway here is simplifying a problem and making semantic vars.

But if I understand the problem clearly I can apply a set of rules without thinking about card game rules per se, but the interviewer would need to supply the back stories such as suits and how one wins...

Collapse
jrtibbetts profile image
Jason R Tibbetts

37

"In a row?"

Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author

Absolutely. It was amazing.

Collapse
evalenzuela profile image
evalenzuela

Magic numbers are pure evil! :D. If you use them and revisit your code in 3 months, you won't remember what were they for.

Collapse
mortoray profile image
edA‑qa mort‑ora‑y Author