You can never guess what this does 👇
function c(a) {
return a / 13490190;
}
const b = c(40075);
console.log(p); // 0.002970677210624906
All we see is a calculation. We have no idea what those numbers and variables mean in the bigger picture of the codebase.
It’s a mystery. It’s magic to everyone apart from the developer who wrote the code.
We can’t possibly make any contributions to this code; we’re stuck.
But what if our dev had looked beyond code that merely works and also focused on communicating the full story of the code?
At the very least they would have given those variables far more descriptive names to explain the context:
function calculateSpeedstersTime(distance) {
return distance / 13490190;
}
const time = calculateSpeedstersTime(40075);
console.log(time); // 0.002970677210624906
This small change vastly improves the code’s readability. Now you have a general idea of what’s going on.
But there are still mysteries we must solve.
It’ll still take us a little bit to realize 13490190
is speed — but how much speed?. And we know 40075
is a distance, but why 40075
of all the numbers?
For maximum readability we replace these magic numbers with variables with explanatory names
const speedstersSpeedKmPerHr = 13490190;
const earthCircumferenceKm = 40075;
function calculateSpeedstersTime(distance) {
return distance / speedstersSpeedKmPerHr;
}
const time = calculateSpeedstersTime(earthCircumferenceKm);
console.log(time); // 0.002970677210624906 ~ 11s
Now you understand every single thing this code does at a glance.
Understanding at a glance is always the goal.
Even with readable code…
Anyone can understand this TS code here; but there’s a serious issue that could easily lead us to hours-long bug hunting.
Can you spot it?
class Person {
state: string;
name: string;
greet() {
console.log(`Hey I'm ${this.name}`);
}
eatFood() {
if (this.state === 'tired') {
this.state = 'fresh';
}
}
exercise() {
if (this.state === 'fresh') {
this.state = 'tired';
}
}
}
Problem: We’re lousily checking the state for equality and assigning with magic strings again and again.
Repeating ourselves, and allow a mere typo to break this code in the future. And the bug won’t always be easy to spot like it would be in this tiny code.
That’s why we have union types; similar to enums in TypeScript and other languages.
class Person {
name: string;
// ✅ Only 3 values, not infinite
state: 'tired' | 'fresh' | 'sleepy';
greet() {
console.log(`Hey I'm ${this.name}`);
}
eatFood() {
if (this.state === 'tired') {
this.state = 'fresh';
}
}
exercise() {
// ✅ Typo: Error thrown
if (this.state === 'fres') {
this.state = 'tired';
}
}
}
Now 'tired'
and 'fresh'
are no longer random string literals. They’re now registered values of a pre-defined type.
And this is one of the powerful advantages TypeScript has over regular JS.
We even have this exact type of issue in a Dart codebase I’m part of. We’ve used the same magic strings like 10 times each.
But Dart only has enums and it’s not so easy to convert from magic strings to enums like we did here with union types. So we better refactor before it comes back to bite us!
The only thing that remains constant…
Keep using magic values and they keep spreading throughout the codebase across more and more files.
Replacing them is a chore.
// email.js
export function sendEmail({ username }) {
emailApi.send({
title: `Hi ${username ?? 'User'}`,
role: 'user' // Union type value
});
}
// user-info.jsx
export function UserInfo({ user }) {
return (
<div>
<div>Name: {user.name ?? 'User'}</div>
<div>Email: {user.email}</div>
</div>
);
}
What happens when you want to change the default user name to something else but you’ve got the 'User'
magic string in over 15 files?
Even Find and Replace will be tricky because they could be other strings with the same value but no relation.
We can fix this issue by creating a separate global config file containing app-wide values like this:
// app-values.js
export const DEFAULT_USERNAME = 'User'; // ✅ Only one place to change
// email.js
import { DEFAULT_USERNAME } from './app-values';
export function sendEmail({ username, role }) {
emailApi.send({
message: `Hi ${username ?? DEFAULT_USERNAME}`, // ✅
role: role ?? 'user',
});
}
// user-info.jsx
import { DEFAULT_USERNAME } from './app-values';
export default function UserInfo({ user }) {
return (
<div>
<div>Name: {user.name ?? DEFAULT_USERNAME}</div> // ✅
<div>Email: {user.email}</div>
</div>
);
}
Final thoughts
Banish magic numbers and strings from your JavaScript code for clarity, maintainability, and efficiency.
By adopting these practices you pave the way for code that is not only functional but also self-documenting, collaborative, and resilient in the face of change.
Every Crazy Thing JavaScript Does
A captivating guide to the subtle caveats and lesser-known parts of JavaScript.