Avoiding Magic Numbers
Magic numbers are an anti-pattern and should generally be avoided. What do I mean when I say magic number? I’m referring to using numbers directly in code as opposed to using a named constant. This can also apply to other data types and literals, especially strings. Why are they bad? They inhibit readability and refactorability.
Readability
One of the big advantages of eliminating magic numbers is it increases readability and helps your code be more self documenting. It allows us to think about the knowledge the number represents rather than worrying about the number itself. Also, when you use a magic number, the intent or meaning of that number is obfuscated and may be unclear.
Consider this snippet of Swift code:
func haveICaughtThemAll(numberCaught: Int) -> Bool {
return numberCaught == 151
}
func howManyMoreDoIHaveToCatch(numberCaught: Int) -> Int {
return 151 - numberCaught
}
It may not be immediately clear what the magic number 151
means or why it was chosen, especially if you aren’t familiar with the problem domain (Pokemon). By extracting the 151
into a named constant, the code is now easier to read and you don’t have to reason about (or worse, guess) what the number 151
means.
let totalPokemon = 151
func haveICaughtThemAll(numberCaught: Int) -> Bool {
return numberCaught == totalPokemon
}
func howManyMoreDoIHaveToCatch(numberCaught: Int) -> Int {
return totalPokemon - numberCaught
}
Refactorability
The other main advantage to avoiding magic numbers is it makes code easier to refactor and less prone to bugs. It accomplishes this by helping us keep the DRY principle: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
Let’s return to the example above.
let totalPokemon = 151
func haveICaughtThemAll(numberCaught: Int) -> Bool {
return numberCaught == totalPokemon
}
func howManyMoreDoIHaveToCatch(numberCaught: Int) -> Int {
return totalPokemon - numberCaught
}
If the value of totalPokemon
ever changes (spoiler alert: it will), you now only have to change it in one place. In the original example, you would have to find all instances of the number 151
and replace them. That may not be so difficult in this example, but in an actual codebase, the usages of this magic number may not be close to one another and you may forget to do a find and replace across your codebase or may not even think you need to. Or, what if you have another instance of the number 151
in your code, but that instance represents the maximum number of hit points a Pokémon can have? If you simply do a find and replace across your whole codebase, you’ll change that 151
erroneously. By using a constant for totalPokemon
, you now have “a single, unambiguous, authoritative representation” of this piece of knowledge in your system. Now, when someone decides that 151 just isn’t enough Pokemon, you only have to change that piece of knowledge in one place, thus preventing bugs throughout your system.
Naming and Duplicate Values
As you remove magic numbers from your code and replace them with named constants, be sure that your constant actually DOES increase readability. Doing this:
let seven = 7
is not helpful. It gives no additional information about what piece of knowledge 7
represents. This brings up another case to consider:
let startingNumberOfCards = 7
let maxNumberOfPlayers = 7
Even though 7
is the value for both of these constants, it represents different knowledge in different contexts and thus 2 different named constants are appropriate.
Exceptions
As with most “rules” in programming, there are exceptions.
0
, 1
and null
/nil
are usually safe to be used on their own. For example:
array.count == 0
array[i + 1]
middleName == nil
firstName == ""
Based on the context in which these literals are used, the meaning is clear. In fact, the meaning may be obfuscated if you used a named constant. If the first example was array.count == emptyArrayCount
, this would suggest that you consider an array “empty” if it has some number of items in it other than 0. If that’s indeed the case, then a named constant may be appropriate. However, if you have let emptyArrayAmount = 0
, it’s probably just going to confuse people reading your code.
Strings that are used for logging or tracing are also an exception; in fact, it’s often preferable to keep them as literals. The general principle is this: if a literal’s meaning is clear from its context and won’t change in the future, it’s probably ok to use. However, that leads us to a gray area.
Gray Area
Here’s a question: should you use a constant like let secondsPerMinute = 60
? This value isn’t going to change anytime soon, so you’re less worried about refactorability. I’d say it depends on the context. In many contexts, secondsPerMinute
will remove any ambiguity about what the magic number 60 means. For example, does 60 mean seconds per minute or minutes per hour? However, if the only usage of secondsPerMinute
is in a calculation for the number of seconds in a workday, then something like
let hoursInTheWorkday = 8
let secondsInTheWorkday = hoursInTheWorkDay * 60 * 60
is probably more readable than
let hoursInTheWorkday = 8
let minutesPerHour = 60
let secondsPerMinute = 60
let secondsInTheWorkday = hoursInTheWorkday * minutesPerHour * secondsPerMinute
especially if you’re not using secondsPerMinute
anywhere else. Note that we’re also relying on the fact that 60 seconds/minute and 60 minutes/hour are fairly universal pieces of knowledge. Something like radiansPerDegree
in the following code might be more appropriate because it’s not as well known of a constant:
let radiansPerDegree = 0.0174533
let radiansInACircle = 360 * radiansPerDegree
Also, specifying mathematical constants such as this or others like pi ensures consistency in your calculations across your code. You wouldn’t want 3.14159
in one place and 3.14
in another, especially if you’re writing code for a rocket or another domain where high precision is important.
Conclusion
While avoiding magic numbers is a simple concept, it will increase the readability and refactorability of your code, thus improving its cleanliness. While this post provides some guidelines, above all, be pragmatic and remember that context matters. While magic numbers should be avoided, don’t dogmatically obliterate all of them in your code without some thought.