Variable Name Antipatterns Named and Explained
- 5 minutes read - 929 wordsThere are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton
Let’s talk about variable name antipatterns!
I use the term ‘antipattern’ a lot on this blog. To make sure I’ve defined it at least once, an antipattern is a technique that seems good but has more bad consequences than good ones.
These are antipatterns because they often make sense in isolation and can propagate through a codebase, to the detriment of developer experience and maintainability of the codebase. If you’re interested in a similar take on this idea but from the ‘pattern’ perspective, check out Pick a Good Name on the Hashrocket blog.
Here are the antipatterns, written in JavaScript.
Antipattern: Shrt Nme (Short Name)
Bad:
const c = user.createdAt;
Better:
const createdAt = user.createdAt;
// or
const { createdAt } = user;
Short variable names are tempting, because who doesn’t like minimalism? Shorter names equal less code, equal faster rocket ship?
The problem with short names is that outside their assignment, they’re
meaningless. What does c
mean? If you’re further down the file or in another
file that imports this constant, it’s unknowable. When I see the variable out
of context, I want to have some idea what it means.
Even when the variable name has a common meaning, such as an enumerable index
named i
or idx
, I think you’re better off with index
.
Don’t use a short name unless a reasonable colleague would understand its meaning out of context.
Antipattern: Looonnng Name (Long Name)
Bad:
const userWhoHasTriedAndFailedToLogIn = this.user;
Better:
const loggedOut = this.user;
⛔️ Contradiction alert! Didn’t I just argue against short names? It’s a game of inches.
Long variable names are tempting because we don’t want to lose information. If this variable represents a user who tried to log in, shouldn’t our name reflect that?
Long variable names make long lines and can be difficult to read. They’re also notoriously good places to hide typos, if you care. I do prefer them to short names, but we’re looking for a middle ground.
Antipattern: Doppelgänger Names
Bad:
const userA = this.admin;
const userB = this.customer;
Better:
const admin = this.admin;
const customer = this.customer;
You find this antipattern in automated tests. userA
and userB
walk into
a customer portal, complete purchaseA
and purchaseB
, acquiring itemA
and itemB
.
I call these Doppelgänger Names because they’re nearly identical, just one
letter different, but they represent distinct ideas. userA
and userB
almost
always have a distinction: a role, a creation date, or a count of associated
records. If they’re both customers, but one is new and one is old, why not name
them newCustomer
and existingCustomer
? Let’s preserve that meaning.
Another benefit of distinct variable names is readability. Your eyes can easily
swap factoryA
and factoryB
; try that with yakima
and columbus
.
Doppelgänger names are also great hiding places for bugs. I’ve lost count of
the tricky bugs I’ve resolved where multiple programmers failed to realize
that a Jest mock of useProfileQuery
cannot mock a function named
useProfilesQuery
(wait for it).
Antipattern: The Name That Knew Too Much
Bad:
// Inside a `ProductItem` component
<button onClick={asyncSubmitStripePayment}>Purchase</button>
Better:
<button onClick={handleClick}>Purchase</button>
I see this all the time in React code: a button in a small isolated component
that when clicked calls a bizarrely-named callback like
asyncSubmitStripePayment
or maybeUpdateClickEventsInGTA
. What’s going on here?
Through the magic of Props Drilling™, these functions were passed through intermediaries, renamed again and again the same name as the original function fathoms above. By the time we see the name in the product item context, it feels out of place.
I like prop names that are appropriate to their context, like this:
// Component definition
const ProductItem = ({ handleClick }) => {
return <button onClick={handleClick}>Purchase</button>
}
// Usage
<ProductItem handleClick={asyncSubmitStripePayment} />
If we bring the button to life, we can have the following conversation:
- Developer: Button, when I click you, what happens?
- Button: I call a function called
handleClick
! - Developer: What does
handleClick
do? - Button: Not my problem.
Don’t let your names know too much about what’s happening in other realms.
Antipattern: Typed Names
Bad:
const usersArray = this.users;
Better:
const users = this.users;
Including types in a variable name is a convention in some languages. But in the many languages where it isn’t a convention, such as Elixir, Ruby, and JavaScript, resist the urge.
Typed variable names are like overly specific code comments. They crystallize
the implementation right now, making future changes harder. What if
this.users
becomes an object? If we’re diligent enough to correctly change
the variable name, now we’re not just changing behavior, we’re changing a name,
too, for no reason.
If you must tell everyone the type of your variable, you might have an issue
with a thing (users
) having a name that’s surprising given its type
(boolean
or string
). Solve that problem instead!
Make Renames Easy
In Make Renames Easy, I showed how I’ve made it trivial to rename variables in my code. A definite beginner-ism in programming is being afraid to rename things: “it works… I don’t want to break it by mistyping up a variable name!” Nobody wants that, so instead of never doing it, we make it foolproof.
Final Thoughts
Names are important. Renaming variables is one of my last passes during building and refactoring. Sometimes you can’t know the best name for a variable until the feature is complete. Even with work I’m very confident in, I always welcome feedback on variable names because it can be hard to see the best idea when you’re close to the work.