Good Naming Is the Cheapest Form of Documentation

by Eric Hanson, Backend Developer at Clean Systems Consulting

The Cost Nobody Measures

When a developer opens an unfamiliar class and spends two minutes figuring out what mgr, proc, and tmp refer to before they can begin understanding the logic, that cost is invisible. It doesn't appear in sprint velocity. It doesn't trigger a Jira ticket. It just accumulates — across dozens of developers, hundreds of file opens, thousands of working hours — until the codebase has a reputation for being "hard to work with" and nobody can articulate exactly why.

Bad naming is a slow tax on your entire team's productivity. Good naming is the rebate.

The return on investment is asymmetric in the best possible way: the cost of choosing a clear name is about fifteen seconds of thought at write time. The benefit is paid forward every time anyone reads that code for the lifetime of the codebase.

What Good Names Actually Do

A good name does one thing: it tells the reader what to expect without requiring them to read the implementation. When a function is named getUserById, you know it takes an ID and returns a user. When it's named fetchUser, you know less — fetch from where? With what key? When it's named processData, you know almost nothing.

Good naming removes the need to read the body to understand the contract. This compounds at scale: a developer navigating a 50,000-line codebase reads mostly names — class names, method names, variable names. The implementation is read only when the name isn't sufficient. If the names are good, most implementation reads are unnecessary.

The Specific Habits

Name variables for what they contain, not how they're used

// What is this?
boolean flag = checkUserStatus(userId);

// Now you know without reading checkUserStatus
boolean isAccountSuspended = checkUserStatus(userId);

flag, result, temp, data — these are placeholders. They communicate that a value exists, not what the value is.

Name functions for what they do, including side effects

// Sounds like a query, actually sends an email
boolean checkAndNotifyUser(User user);

// Honest name
boolean notifyUserIfEligibleForUpgrade(User user);

If a function sends an email, writes to a database, or calls an external API, its name should reflect that. Functions named as pure queries that have side effects are a category of bug waiting to happen.

Name booleans as predicates

// Reads naturally in an if statement
if (user.isEligibleForDiscount()) { ... }
if (order.hasExpiredItems()) { ... }
if (transaction.wasProcessedSuccessfully()) { ... }

// Doesn't read naturally
if (user.eligibleDiscount()) { ... }
if (order.expiredItems()) { ... }

A boolean variable or function should read as a yes/no question. is, has, was, can, should — these prefixes communicate both the type and the intent.

Name constants to replace magic values

// What is 86400? Why this number?
if (sessionAge > 86400) expire();

// Immediately understood
static final int SESSION_MAX_DURATION_SECONDS = 86400;
if (sessionAge > SESSION_MAX_DURATION_SECONDS) expire();

Magic numbers and magic strings are unnamed assumptions. Constants are named constraints. When a constant needs to change, you change one thing in one place, and every reader knows what changed and why.

The Renaming Refactor

One of the most underrated refactors is a rename with no other changes. Take a function named handle() and rename it to validateAndPersistIncomingPaymentEvent(). No logic changes. The codebase is meaningfully better — every call site is now self-documenting, and the implementation is less likely to be modified incorrectly because the name communicates what it should and shouldn't do.

This kind of refactor is nearly zero-risk (with IDE rename support), takes under a minute, and pays dividends indefinitely.

The Naming Anti-Patterns Worth Calling Out

Encoding types in names: userList, orderMap, configString — when your type system already expresses the type, the name is redundant noise. The type annotation or generic parameter already says List<User>; userList adds nothing.

Abbreviation that saves four characters and costs comprehension: usrMgr, cfg, txn. The keystrokes you save in writing are not worth the cognitive overhead on every read. IDEs autocomplete. Abbreviations don't.

Names that reflect implementation rather than intent: processByForLoop, sortWithQuicksort — these expose how, not what. When the implementation changes, the name becomes misleading.

Vague domain terms: Manager, Handler, Processor, Service, Helper — these are not names, they are job descriptions. An OrderProcessor processes orders, which describes roughly half the codebase. OrderTaxCalculationService, OrderFulfillmentCoordinator — these communicate actual roles.

The Practical Takeaway

In your next PR, spend five minutes specifically reviewing names — not logic, not structure, just names. Find every instance of data, result, flag, temp, mgr, or similarly vague identifiers. Rename each one to describe what it actually contains. Submit the rename as a separate commit with no logic changes. Note in code review whether the diff is easier to read after than before.

It will be.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

The Difference Between a Test Suite That Gives Confidence and One That Just Passes

A passing test suite and a trustworthy test suite are not the same thing. The difference comes down to whether your tests are designed to catch failures or designed to avoid them.

Read more

Multi-Stage Builds: The Dockerfile Trick That Shrinks Your Image

Multi-stage builds let you use a full build environment to compile your application and then copy only the result into a minimal runtime image — eliminating build tools, source code, and intermediate artifacts from what you actually ship.

Read more

Every Senior Developer Was Once Confused by the Same Things You Are

The things that feel most confusing in early and mid-career engineering — distributed systems, production incidents, architectural tradeoffs — are confusing to everyone until they aren't. The path through is exposure, not aptitude.

Read more

What Separates a €50/hr Contractor From a €150/hr Contractor

The gap between a €50 rate and a €150 rate is not three times the technical skill. It is a specific combination of positioning, communication, and demonstrated value that most contractors are never taught to build.

Read more