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 )
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] )
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] )
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.
In this code:
for i in 0...52:
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:
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 deckto 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] )
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)/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 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.
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] )
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