Last month I lost forty-five minutes to a payment processing bug. The culprit was a variable called amount. Not amountCents. Not amountInLowestDenomination. Just amount. The function that set it assumed dollars; the function that read it assumed cents. Both assumptions made sense in isolation. Together, they subtracted $99.35 from a customer’s bank account instead of $0.99.

Once we spotted it, the fix took fifteen seconds. The other forty-four minutes and change went to reconstructing whatever mental model the original developer held when they picked that name. Names are interfaces. When they’re ambiguous, every reader reverse-engineers your intent. When they’re wrong, they become lying documentation that still compiles.

The Interface You Didn’t Know You Had

Most engineers treat naming like a style exercise. Pick something descriptive, keep it brief, follow the team convention, move on. That misses the point. Every name you choose is a promise to every other developer who will read your code. It’s an API call with no type checker, no compiler enforcement, and zero deprecation warnings when the meaning drifts.

Think about what makes a decent REST API. Consistency: if GET /users returns a list, GET /orders should too. Predictability: nobody should need to read the implementation to guess what an endpoint does. Discoverability: a developer ought to find what they need by pattern, not by spelunking through source files. Clear error handling: when something breaks, the response should say exactly what and where.

Now audit the names in your codebase against those same criteria.

Consistency: do you have fetchUser in one module and getCustomer in another for the identical operation? Predictability: does processTransaction create, update, or validate? Discoverability: can a new team member guess the function that calculates shipping costs, or do they grep for “ship” and pray? Error handling: when a name misleads, how long until someone notices the gap between the label and the behavior?

The naming conventions scattered through your codebase are your team’s real API. They’re the interface every developer uses to interact with every piece of the system. And most teams design that interface by accident.

Character Names and Variable Names Are the Same Problem

Fiction writers wrestle with a version of this. Name a character “Sarah” in chapter one, and every later mention of “Sarah” lugs along everything the reader knows about her. Change her personality without changing the name and you’ve created a continuity error. Introduce a second character with a name too close—“Sara” without the ‘h’—and you’ve muddied the reader’s mental map. The name is the interface to the character.

That’s why plenty of writers reach for a character naming tool during planning. Not because they can’t think of names, but because systematic naming forces you to reckon with consistency, tone, and meaning before you commit. Reedsy’s Character Name Generator gets this: it’s not a random word dispenser. It’s a constraint engine. You supply genre, gender, origin, and it returns names that fit a coherent world. Discoverability is baked in. You don’t need to know every possible name; you need to know the system that produces them.

Software naming needs the same deliberate treatment. Not a tool that picks names for you, but a framework that makes your naming choices intentional, auditable, and consistent across the codebase. The aim isn’t clever names. It’s names that don’t make your teammates curse you at 3 AM.

What Bad Names Actually Cost

I’ve watched teams burn weeks on a migration because a database column called status was reused across three different workflows with three different meanings. The column wasn’t wrong—just overloaded. Every query had to join against context tables to figure out which status it was dealing with. The fix required renaming the column and updating forty-seven references. The original sin was believing “status” was descriptive enough.

Purdue’s OWL resource frames this well: in creative writing, character development leans heavily on consistency and clarity. A character who acts against their established traits without explanation breaks the reader’s trust. A variable that shifts meaning depending on context breaks a developer’s trust. Same failure mode, different medium.

The costs of bad names are measurable:

  • Onboarding tax. Every new team member spends their first weeks building a mental dictionary of what things are actually called versus what the code says they’re called. I’ve onboarded onto teams where the internal wiki had a “glossary of misleading names” page. That page existed because nobody ever fixed the names.
  • Bug creation rate. Ambiguous names cause bugs when the reader’s interpretation doesn’t match the writer’s intent. These aren’t logic errors. They’re communication errors. The code does exactly what it was written to do; the name just lies about it.
  • Refactoring friction. When you can’t trust names, every change requires reading the implementation. A one-line fix balloons into a thirty-minute investigation. Multiply by the number of times your team touches the codebase in a given week.
  • Review fatigue. Reviewers stop flagging confusing names because there are too many to flag. The standard drops. New code copies the existing patterns. The problem compounds.

A Framework for Auditing Names Like an API

Here’s the exercise I run with teams that are ready to take naming seriously. It eats about two hours and it permanently shifts how they see their codebase.

Step 1: Pick a bounded context. Don’t try to audit the whole system. Grab one module, one service, or one package. Something you can hold in your head. The goal is depth, not coverage.

Step 2: List every public name. Function signatures, exported types, API endpoint paths, database table and column names, message queue topics, environment variables. Anything that crosses a boundary—whether that boundary is a process, a module, or just a file.

Step 3: Run API design questions against each name.

  • Is it consistent? Does the same concept carry the same name everywhere? If you have createUser in one spot and registerAccount in another, pick one and rename.
  • Is it predictable? Can someone guess what it does without reading the implementation? If the function is called validate, does it return a boolean, throw an exception, or hand back a list of errors? The name should tell you.
  • Is it discoverable? If I’m hunting for the thing that sends an email, will I find it by searching for “send” or “email” or “notify”? Your team should agree on the canonical verb and stick to it.
  • Does it handle errors clearly? When a name is wrong, how will the next developer know? Will the type system catch it? Will a test fail with a clear message? Or will it silently produce incorrect behavior until a customer complaint surfaces?

Step 4: Score each name. I use a simple scale: clear (the name and behavior match), tolerable (the name is slightly off but unlikely to cause confusion), and misleading (the name actively contradicts the behavior or hides critical information). Tolerable names are not acceptable. They’re just the ones that haven’t hurt anyone yet.

Step 5: Fix the misleading ones now. Don’t add them to a backlog. Don’t create a ticket for “someday.” Rename them immediately. If you’re worried about breaking changes, add the new name alongside the old one with a deprecation comment and a removal date. Treat it like an API deprecation—because that’s what it is.

The Hardest Names to Get Right

Some names resist easy fixes. Here are the ones I see teams stumble over most often, and what’s worked for me.

Booleans that aren’t questions. A variable called active is ambiguous. Active in what sense? Currently logged in? Subscription not expired? Record not soft-deleted? Rename it to answer a specific question: isSubscriptionActive, hasValidSession, isNotSoftDeleted. Yes, the last one reads awkwardly. Awkward beats ambiguous. You can also flip the polarity: isDeleted is clearer than active when the default state is “not deleted.”

Functions with side effects. If a function called getOrderTotal also updates a cache, the name lies. Either rename it to getOrderTotalAndUpdateCache (ugly but honest) or split it into two functions. The second option is almost always better. A function should do one thing that its name describes completely.

Database columns that encode meaning in position. I’ve seen tables where data_1, data_2, and data_3 hold different information depending on the value of a type column. This is a polymorphic association implemented without the polymorphism. Every query becomes a puzzle. The fix is either separate columns with meaningful names or separate tables. The short-term migration pain is worth the long-term readability gain.

Configuration values with no units. timeout: 30. Seconds? Milliseconds? Minutes? The name must include the unit: timeoutSeconds, timeoutMs. Better yet, use a type that enforces the unit. Better still, name it for what it actually controls: databaseConnectionTimeoutSeconds. Long names are a tax you pay once. Ambiguous names are a tax every reader pays forever.

When Renaming Feels Too Expensive

The most common pushback I hear: “We can’t rename that, it’s used everywhere.” That’s usually true and almost never a reason to leave it alone. Names used everywhere are the highest-leverage names to fix. A misleading name that appears in fifty files causes confusion every single time someone reads any of those files. The rename cost gets paid once; the bad-name tax gets paid continuously.

Real constraints exist. Database column renames need migrations. Public API field names need versioning. Environment variables need coordination with infrastructure teams. These aren’t reasons to avoid renaming. They’re reasons to plan the rename carefully.

For internal code, modern IDEs make renaming trivial. For external interfaces, add the new name, support both through a deprecation window, then remove the old one. This is exactly what you’d do for an API endpoint you wanted to improve. Your variable names deserve the same treatment.

The Character Naming Tool Mindset

Here’s what I actually mean by that metaphor. A character naming tool doesn’t invent names from thin air. It applies constraints—genre, era, culture—and produces options that are internally consistent. The writer still makes the final choice, but the tool prevents the worst mistakes: names that clash, names that break the world’s rules, names that confuse the reader.

Your team needs the equivalent. Not a piece of software, but a shared set of constraints that make naming decisions predictable and auditable. Here are the constraints that have worked on teams I’ve been part of:

  • A team glossary. A living document that defines every domain term and its canonical name. “Customer” versus “User” versus “Account”—pick one per concept and write it down. When someone introduces a new term, they must add it to the glossary. When a term changes meaning, the glossary changes first, then the code.
  • A naming style guide. Not a generic style guide pulled from the internet. A document that says things like: “Functions that return booleans start with ‘is’, ‘has’, or ‘can’.” “Database columns that hold monetary values end with the currency and denomination: ‘priceUsdCents’.” “Async functions end with ‘Async’ only if the language doesn’t enforce it at the type level.” Specific rules that answer specific questions your team actually faces.
  • A review checklist item. Add “Are the names clear and consistent with the glossary?” to your pull request template. Reviewers should block merges on misleading names the same way they’d block on a missing null check. The standard has to be enforced or it doesn’t exist.
  • A rename budget. Explicitly allocate time for renaming. Not as a separate project, but as part of normal development. When you touch a file and see a bad name, rename it. When you’re planning a sprint, include time for the renames you know you need. If your team can’t afford to rename things, your naming debt is too high and you need to address it directly.

What Happens When You Get This Right

A codebase with good names feels different. You can read a function signature and know what it does. You can search for a concept and find all the relevant code. You can onboard a new developer and they’ll be productive in days instead of weeks because the code matches the documentation matches the names.

This isn’t hypothetical. I’ve watched teams cut their bug rate by measurable amounts just by renaming the worst offenders in their codebase. One team I worked with had a recurring production incident caused by a misnamed configuration flag. The flag was called enableCaching. It actually controlled whether the cache was populated, not whether it was read. When the cache sat empty—which happened during deployments—disabling the flag didn’t help because the code still tried to read from the cache. Renaming it to populateCacheOnWrite and adding a separate readFromCache flag eliminated that entire class of incident. The fix was three lines of code and two renamed variables. The impact was months of avoided pages.

Start With One Name

Don’t try to fix your entire codebase’s naming in one pass. That’s a recipe for a failed initiative and a trail of half-completed renames. Instead, pick the one name that has caused the most confusion in the last month. The variable everyone misreads. The function whose behavior surprises new hires. The column that shows up in every tricky bug report.

Rename it. Update every reference. Drop a quick note in your team channel about why the old name was misleading and what the new name means. Treat it like an API change announcement, because that’s exactly what it is.

Then do it again next week. And the week after. Naming is maintenance. It’s not a one-time design decision you make at project start and never revisit. The code changes, the domain evolves, and names that were accurate six months ago become misleading today. Your naming practice needs to evolve with the code.

Here’s the question I want you to carry to your team: what’s the most misleading name in the code you’re working on right now, and what would you rename it to if you could? If you can’t answer that, you haven’t been paying attention. If you can answer but haven’t fixed it yet, ask yourself what’s stopping you. Usually, the answer is inertia. Inertia is not a design principle.

Your variable names are someone else’s API. Design them accordingly.