Good Naming Is the Cheapest Form of Documentation

by Arif Ikhsanudin, Backend Developer

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

When Laptops Are Domain-Locked and Developers Can’t Install Tools

Nothing kills momentum faster than a laptop you can’t fully control. As contractors, working on a domain-locked machine is often a recipe for frustration.

Read more

How to Undo Almost Anything in Git Without Panicking

Git is one of the most forgiving version control systems ever built — but only if you know where to look. Most "I ruined everything" moments are recoverable in under five minutes.

Read more

Designing with Java Enums — When They're the Right Model and When They're Not

Java enums are more capable than most developers use them for, but that capability has limits. Here is a clear-eyed look at what enums do well, where they break down, and the design decisions that determine which side you end up on.

Read more

The Risks of Shipping Code Without Review

Shipping code without a review might feel fast and efficient—but it’s a trap. One missed bug can ripple through your system and your team.

Read more