Chapter 2: Meaningful Names
Status: Notes complete
Difficulty: Easy
Time to complete: ~40 min read
Overview
Names are everywhere in code — variables, functions, arguments, classes, packages, source files, directories. Because we name so much, naming well is one of the highest-leverage skills a developer can have. This chapter provides 16 concrete rules that transform obscure code into self-documenting prose. None are controversial; all require practice to make habitual.
The Problem: What Bad Code Looks Like
A user authentication service with poor naming:
// BAD — cryptic names throughout
public class UAS {
private DB d;
private List<String[]> data;
public boolean chk(String u, String pw) {
String[] r = d.get(u);
if (r != null && r[1].equals(pw) && !r[2].equals("1")) {
data.add(new String[]{u, String.valueOf(System.currentTimeMillis())});
return true;
}
return false;
}
}Every name here — UAS, d, data, chk, u, pw, r — forces the reader to build a mental translation table before understanding a single line. The 16 rules that follow eliminate this entirely.
Core Principles
1. Use Intention-Revealing Names
Why: A name that requires a comment to explain it has already failed its job. The name should answer three questions: why it exists, what it does, and how it is used.
// BAD — what is d? days? distance? discount?
int d;
// BAD — what list is this? flagged for what?
List<int[]> theList = new ArrayList<>();
for (int[] x : theList) {
if (x[0] == 4) flagged.add(x);
}
// GOOD — the name is the documentation
int elapsedTimeInDays;
// GOOD — names make the game board logic read like prose
List<Cell> gameBoard = new ArrayList<>();
for (Cell cell : gameBoard) {
if (cell.isFlagged()) flaggedCells.add(cell);
}// BAD
int d;
std::vector<std::array<int, 3>> theList;
// GOOD
int elapsedTimeInDays;
std::vector<Cell> gameBoard;
for (const auto& cell : gameBoard) {
if (cell.isFlagged()) flaggedCells.push_back(cell);
}# BAD
d = None
the_list = []
for x in the_list:
if x[0] == 4:
flagged.append(x)
# GOOD
elapsed_time_in_days: int = 0
game_board: list[Cell] = []
for cell in game_board:
if cell.is_flagged():
flagged_cells.append(cell)2. Avoid Disinformation
Why: Names that lie or mislead are worse than cryptic names. A cryptic name delays understanding; a misleading name causes active misunderstanding and bugs.
Examples of disinformation:
- Naming a
HashSet<Account>asaccountList— readers will assumeListbehavior (get(index), ordering guarantees) - Using names that differ only by one character:
XYZControllerForEfficientHandlingOfStringsvsXYZControllerForEfficientStorageOfStrings— nearly impossible to distinguish at a glance - Using
l(lowercase L) orO(uppercase o) as variable names — they look like1and0
// BAD — not a List; accountList implies List behavior
HashSet<Account> accountList = new HashSet<>();
// GOOD — accurate to the actual type
Set<Account> accountGroup = new HashSet<>();
// or
Map<String, Account> accountsByEmail = new HashMap<>();
// BAD — l looks like 1; O looks like 0
int l = 1;
if (O == l) { ... }
// GOOD
int lineCount = 1;
if (orderCount == lineCount) { ... }// BAD
std::unordered_set<Account> accountList;
// GOOD
std::unordered_set<Account> accountSet;
std::unordered_map<std::string, Account> accountsByEmail;3. Make Meaningful Distinctions
Why: If two things are different, they need names that communicate how they differ. Noise words and number series add characters without adding meaning.
Number series (a1, a2, a3) communicate nothing about the relationship between arguments:
// BAD — what is a1? what is a2?
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
// GOOD — source and destination are unambiguous
public static void copyChars(char[] source, char[] destination) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}Noise words (ProductInfo vs ProductData, TheProduct vs Product, ProductObject vs Product) are indistinguishable:
// BAD — what is the difference between these?
class Product { ... }
class ProductInfo { ... } // Info vs nothing — meaningless distinction
class ProductData { ... } // Data vs nothing — also meaningless
// BAD — noise word "The"
Product theProduct = getProduct();
// GOOD — pick one name per concept; distinguish by actual role
class Product { ... }
class ProductSearchResult { ... } // a specific, distinct concept
class ProductCatalogEntry { ... } // another specific concept4. Use Pronounceable Names
Why: Programming is a social activity. Code is discussed in meetings, in code reviews, and on calls. You cannot discuss genymdhms without sounding absurd.
// BAD — unpronounceable; conversation becomes "gee-en-why-em-dee-aitch-em-ess"
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
}
// GOOD — you can say these in a meeting
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
}5. Use Searchable Names
Why: You spend more time searching for code than writing it. A single-letter name or a bare number is impossible to grep for. e will match everything; MAX_CLASSES_PER_STUDENT will find exactly what you need.
Rule: single-letter names are acceptable only as loop counters in tiny scopes (for (int i = 0; i < 5; i++)). Any name that will be searched for — constants, domain values, configuration — must be a named symbol.
// BAD — what does 7 mean? Cannot grep for it meaningfully
for (int j = 0; j < 34; j++) {
s += (t[j] * 4) / 5;
}
// GOOD — every constant has a name that explains its meaning
int realDaysPerIdealDay = 4;
final int WORK_DAYS_PER_WEEK = 5;
final int MAX_CLASSES_PER_STUDENT = 7;
int taskEstimate = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = realTaskDays / WORK_DAYS_PER_WEEK;
sum += realTaskWeeks;
}# BAD
for j in range(34):
s += (t[j] * 4) / 5
# GOOD
REAL_DAYS_PER_IDEAL_DAY = 4
WORK_DAYS_PER_WEEK = 5
for j in range(NUMBER_OF_TASKS):
real_task_days = task_estimates[j] * REAL_DAYS_PER_IDEAL_DAY
real_task_weeks = real_task_days / WORK_DAYS_PER_WEEK
total_weeks += real_task_weeks6. Avoid Encodings
Why: Modern IDEs eliminate all the problems that encoding schemes were invented to solve. Hungarian Notation was invented for C compilers that did not enforce type safety; it is noise in a type-safe language. Member prefixes (m_) are redundant when you have syntax highlighting and auto-complete.
Hungarian Notation — avoid entirely:
// BAD — Hungarian Notation (type encoded in name)
String strFirstName;
int intAge;
boolean bIsActive;
PhoneNumber phoneString; // type changes but the name doesn't
// GOOD
String firstName;
int age;
boolean isActive;
PhoneNumber phone;Member prefixes — redundant and noisy:
// BAD — m_ prefix
public class Customer {
private String m_firstName;
private String m_lastName;
public String getDescription() {
return m_firstName + " " + m_lastName;
}
}
// GOOD — no prefix needed
public class Customer {
private String firstName;
private String lastName;
public String getDescription() {
return firstName + " " + lastName;
}
}Interface vs. implementation naming — do not encode “I” in interfaces:
// BAD — IShapeFactory exposes the implementation detail that it is an interface
interface IShapeFactory { Shape createShape(String type); }
class ShapeFactory implements IShapeFactory { ... }
// GOOD — the interface has the clean name; the implementation gets the encoding if needed
interface ShapeFactory { Shape createShape(String type); }
class ShapeFactoryImp implements ShapeFactory { ... }
// or use a domain-specific name
class BitmapShapeFactory implements ShapeFactory { ... }// BAD
class IShapeFactory {
public:
virtual std::unique_ptr<Shape> createShape(const std::string& type) = 0;
};
// GOOD
class ShapeFactory {
public:
virtual std::unique_ptr<Shape> createShape(const std::string& type) = 0;
};
class BitmapShapeFactory : public ShapeFactory { ... };# BAD
from abc import ABC, abstractmethod
class IShapeFactory(ABC):
@abstractmethod
def create_shape(self, shape_type: str) -> Shape: ...
# GOOD
class ShapeFactory(ABC):
@abstractmethod
def create_shape(self, shape_type: str) -> Shape: ...
class BitmapShapeFactory(ShapeFactory):
def create_shape(self, shape_type: str) -> Shape: ...7. Avoid Mental Mapping
Why: A name that requires the reader to translate it to its real meaning forces a constant cognitive load. The reader must hold both the variable name and its “actual” meaning in working memory simultaneously.
Single-letter variable names in loop counters (i, j, k) are fine because the meaning is universally understood. Single-letter names for domain concepts (r for a URL, u for a user, a for an account) are not.
// BAD — what is r? a result? a route? a URL?
String r = httpClient.get(endpoint);
if (r != null && r.contains("200")) {
processResponse(r);
}
// GOOD — no mental translation needed
String responseBody = httpClient.get(endpoint);
if (responseBody != null && responseBody.contains("200")) {
processResponse(responseBody);
}8. Class Names Should Be Nouns
Why: Classes represent things — entities, concepts, roles. A class name should tell you what the object is, not what it does. Names like Manager, Processor, Data, and Info are too vague — they describe almost any class and reveal nothing specific.
// BAD — Manager, Processor, Info, Data are noise words for class names
class OrderManager { ... }
class DataProcessor { ... }
class UserInfo { ... }
// GOOD — noun phrases that describe the entity precisely
class Order { ... }
class OrderRepository { ... }
class PaymentProcessor { ... } // Processor acceptable when describing a clear pattern role
class UserAccount { ... }
class AddressParser { ... }
class InvoiceGenerator { ... }// BAD
class DataProcessor { /* ... */ };
// GOOD
class Order { /* ... */ };
class OrderRepository { /* ... */ };
class InvoiceGenerator { /* ... */ };9. Method Names Should Be Verbs
Why: Methods represent actions — they do something. A method name should tell you what action occurs. Accessors, mutators, and predicates have well-known prefixes that set reader expectations instantly.
// BAD — not clear whether these read or write; no consistent prefix
String name(); // getter? factory method?
void process(); // process what? how?
boolean status(); // returns a boolean? a string?
// GOOD — verb prefix signals the intent
String getName();
void setName(String name);
boolean isActive();
boolean hasPermission(String role);
void postPayment(Payment payment);
void deletePage(Page page);
Order createOrder(Cart cart);
// GOOD — factory methods use descriptive static factory names
Complex fromRealNumber(double real); // preferred over new Complex(5.0)
User withDefaultPermissions(String email);// BAD
std::string name() const;
void process();
// GOOD
std::string getName() const;
void setName(const std::string& name);
bool isActive() const;
void postPayment(const Payment& payment);# BAD
def name(self): ...
def process(self): ...
def status(self) -> bool: ...
# GOOD
def get_name(self) -> str: ...
def set_name(self, name: str) -> None: ...
def is_active(self) -> bool: ...
def has_permission(self, role: str) -> bool: ...
def post_payment(self, payment: Payment) -> None: ...10. Don’t Be Cute
Why: Clever or humorous names work only if every reader shares the reference. Joke names become confusing jargon.
// BAD — cute names that obscure meaning
void HolyHandGrenade(); // Monty Python reference
void whack(); // slang for "kill"
void eatMyShorts(); // Bart Simpson reference
// GOOD — boring is better
void deleteItems();
void kill();
void abort();Rule: choose clarity over entertainment. Code is read under stress, in debug sessions, at 11pm. The reader does not want to decode humor.
11. Pick One Word Per Concept
Why: Using fetch, retrieve, and get as synonyms across different classes forces the reader to wonder if they do different things. Consistency is kindness.
// BAD — three verbs doing the same thing across three classes
class UserService {
User fetchUser(long id) { ... }
}
class OrderService {
Order retrieveOrder(long id) { ... }
}
class ProductService {
Product getProduct(long id) { ... }
}
// GOOD — one verb per concept, applied consistently
class UserService {
User getUser(long id) { ... }
}
class OrderService {
Order getOrder(long id) { ... }
}
class ProductService {
Product getProduct(long id) { ... }
}Similarly: pick controller, manager, or driver — not all three for the same role across different modules.
12. Don’t Pun
Why: “Pick one word per concept” can be misapplied. add should not mean “add to a collection” in one class and “concatenate two strings” in another. That is not consistency — it is punning. Use append or insert for the second case.
// BAD — "add" means two different things
class ShoppingCart {
void add(Item item) { items.add(item); } // adds to a list
}
class StringBuilder {
void add(String text) { buffer += text; } // concatenation — should be "append"
}
// GOOD — different concepts get different names
class ShoppingCart {
void addItem(Item item) { items.add(item); }
}
class StringBuilder {
void append(String text) { buffer += text; }
}13. Use Solution Domain Names
Why: Code is read by developers. Developers know CS terminology. Using well-known patterns and data structure names communicates precisely and concisely.
// GOOD — solution domain names
class AccountVisitor { ... } // Visitor pattern
class JobQueue { ... } // Queue data structure
class ConfigurationFactory { ... } // Factory pattern
class EventBus { ... } // Pub-sub pattern
int[] sortedPageIndices; // "sorted" = meaningful to a developerWhen a well-known CS term accurately describes something, use it. It is precise, searchable, and immediately meaningful to the reader.
14. Use Problem Domain Names
Why: When no CS term applies, use the language of the problem domain. A developer who does not know the domain can ask the domain expert. A developer who sees obscure internal jargon has nobody to ask.
// GOOD — problem domain names in a banking context
class AmortizationSchedule { ... }
double annualPercentageRate;
boolean isNsfFee; // NSF = non-sufficient funds; banking term
class FiduciaryAccount { ... }The rule of thumb: if it has a name in the business domain, use that name. Consistency between the codebase and the domain vocabulary makes conversations between developers and domain experts productive.
15. Add Meaningful Context
Why: Some names are ambiguous without context. state by itself could be application state, UI state, or a US state. When a name lives alone it lacks the context that a class or function can provide.
// BAD — what does "state" mean here?
String firstName;
String lastName;
String street;
String city;
String state; // ambiguous — US state? application state?
String zipCode;
// GOOD — class provides context; member names inherit it
class Address {
String firstName;
String lastName;
String street;
String city;
String state; // now unambiguous — geographic state in the Address context
String zipCode;
}
// GOOD — prefix as last resort when you cannot use a class
String addrState;
String addrCity;
String addrZip;Context also applies to methods: a function named printGuessStatistics with variables number, verb, and pluralModifier is harder to read than if those variables are grouped into a GuessStatisticsMessage class that has a print() method.
16. Don’t Add Gratuitous Context
Why: Rule 15 (add context) and Rule 16 (remove unnecessary context) must be balanced. Prefixing every class in a “Gas Station Deluxe” application with GSD is adding noise, not clarity.
// BAD — GSD prefix adds nothing; everyone knows what app this is in
class GSDAccountAddress { ... }
class GSDCustomerProfile { ... }
class GSDPaymentProcessor { ... }
// GOOD — shorter, cleaner names are easier to read and type
class AccountAddress { ... }
class CustomerProfile { ... }
class PaymentProcessor { ... }Similarly: Address is better than AccountAddress if address is always an account address in this context. MailingAddress is better when there are also BillingAddress and ShippingAddress.
Shorter names are superior to longer names as long as they remain clear. Add context only when the shorter name would genuinely be ambiguous.
Comparison / Summary Table
| Rule | Bad Example | Good Example | Why It Matters |
|---|---|---|---|
| Intention-revealing names | int d | int elapsedTimeInDays | No comment needed |
| Avoid disinformation | accountList (a HashSet) | accountGroup | Prevents false assumptions |
| Meaningful distinctions | copyChars(a1, a2) | copyChars(source, dest) | Arguments communicate their roles |
| Pronounceable names | genymdhms | generationTimestamp | Code is discussed aloud |
| Searchable names | 7 (bare number) | MAX_CLASSES_PER_STUDENT | Grep finds it; intent is clear |
| Avoid encodings | IShapeFactory | ShapeFactory | Modern IDEs make encoding redundant |
| Avoid mental mapping | String r (a URL) | String responseBody | No translation table in reader’s head |
| Class names = nouns | DataProcessor | InvoiceGenerator | Classes are things, not actions |
| Method names = verbs | boolean status() | boolean isActive() | Methods are actions |
| Don’t be cute | whack() | kill() | Clarity over humor |
| One word per concept | fetch/retrieve/get | get everywhere | Consistency reduces cognitive load |
| Don’t pun | add (means two things) | add vs append | Same word = same meaning |
| Solution domain names | XHandler | XVisitor | CS terminology is precise |
| Problem domain names | (internal jargon) | Domain term | Domain experts can verify |
| Add context | String state alone | Address.state | Removes ambiguity |
| No gratuitous context | GSDAccountAddress | AccountAddress | Shorter = cleaner |
When to Apply / Common Exceptions
Always apply:
- Intention-revealing names — no exceptions; this is the foundational rule
- Avoid disinformation — a misleading name is always worse than a neutral one
- Class and method naming conventions — every public API
Use judgment for:
- Single-letter names — acceptable for loop counters (
i,j), mathematical formulae where the letter is the conventional symbol (x,yin geometry code), and lambda parameters in very short one-liners - Domain names — use problem domain names only if the team knows the domain; if onboarding across domains is common, prefer solution domain names
- Context prefixes — use
addrprefix only when you truly cannot use a class; classes are almost always the right answer
Not a license for:
- Over-long names in the name of precision:
getUserByEmailAddressFromDatabaseRepositoryis not better thanfindUserByEmail - Abbreviations disguised as “short searchable names”:
MXCLSis not a searchable name;MAX_CLASS_SIZEis
Checklist
- Does every variable name reveal why it exists, what it does, and how it is used?
- Does every class name sound like a noun that describes a thing or concept?
- Does every method name start with a verb that describes the action it performs?
- Are there any names that differ only in noise words (Info, Data, Object, The)?
- Are there any bare numbers or single-letter names outside of loop counters?
- Have all Hungarian Notation prefixes (
m_, type prefixes) been removed? - Is there exactly one word for each concept across the entire codebase (no
fetch/retrieve/getmix)? - Do any names mislead by implying the wrong type or behavior?
- Is there any gratuitous application-wide prefix on class names?
- Can every name in this file be spoken aloud clearly in a code review?
Key Takeaways
- Intention-revealing names eliminate comments: if a name needs a comment, the name has failed its job.
- Disinformation is worse than obscurity —
accountListfor aHashSetactively misleads; name the type accurately. - Meaningful distinctions require real differences:
ProductDataandProductInfoare indistinguishable noise; useProductSearchResultandProductCatalogEntry. - Pronounceable and searchable names matter because code is social: it is discussed in reviews and searched with grep.
- Avoid encodings — Hungarian Notation and member prefixes (
m_) were invented for tools that no longer exist; modern IDEs make them pure noise. - Class names are nouns (
Account,InvoiceGenerator); method names are verbs (getAccount,generateInvoice); violating this confuses readers about whether a call returns a value or performs an action. - One word per concept:
get,fetch, andretrievedoing the same thing across different classes forces readers to wonder if they differ — they should not have to wonder. - Don’t pun: the same word must always mean the same thing —
addfor list insertion andaddfor concatenation is a pun that will cause bugs. - Context comes from classes:
stateinside anAddressclass needs no prefix; a barestatevariable at method scope may needaddrState— but the class is almost always the better solution. - Shorter is better, as long as it is clear:
AddressbeatsGSDAccountAddress; the goal is precision, not verbosity.
Related Resources
- ch01-clean-code — The philosophical foundation: why names matter in the first place
- ch03-functions — Naming at the function level: do-one-thing and descriptive verbs
- ch05-formatting — How visual structure reinforces good naming
- ch09-unit-tests — Good test names are intention-revealing names at the test level
Last Updated: 2026-04-14