Skip to main content

Meaningful Names

Naming is one of the few things every programmer does constantly, in every language, in every file. Variables, functions, classes, modules, files - all of it gets named, and because naming happens so often, getting a little better at it pays off everywhere at once. This chapter collects a set of concrete rules for picking names, not as style preferences but as a direct lever on how easy code is to read.

Reveal intent

A name should answer the questions a reader would otherwise have to ask: why does this variable exist, what does it hold, how is it meant to be used? If understanding a name requires a comment next to it, the name has already failed. d for "elapsed time in days" tells you nothing; elapsed_time_in_days tells you everything the comment was trying to say, without needing the comment.

The effect compounds across a whole function. A loop over the_list that checks x[0] == 4 is technically simple - three variables, one condition - but it's completely opaque, because none of the domain knowledge is visible in the code. Once you know this is a minesweeper board and the cells are being filtered for a "flagged" status, renaming things to say so turns opaque code into obvious code, with no change to the underlying logic.

# What does this function do? You can't tell from the names.
def get_them(the_list):
result = []
for x in the_list:
if x[0] == 4:
result.append(x)
return result
# Same logic, but the names carry the meaning: a minesweeper
# board, made of cells, filtered down to the flagged ones.
def get_flagged_cells(game_board):
flagged_cells = []
for cell in game_board:
if cell.is_flagged():
flagged_cells.append(cell)
return flagged_cells

Avoid disinformation and false distinctions

Some naming problems come from names that actively mislead, and some come from names that merely fail to distinguish anything.

Disinformation is a name that carries an entrenched meaning different from the one you intend. Calling a variable account_list implies a specific data type - if it's actually a set or a generator, the name is now lying to anyone who reads it. Similarly, naming two very similar things with almost-identical spellings (...ForEfficientHandlingOfStrings vs. ...ForEfficientStorageOfStrings) creates a trap for a skimming reader, and a single lowercase l next to an uppercase O in the same expression is close to unreadable.

# "accountList" implies a List type - fine, until the
# container becomes a set or a generator and the name lies.
account_list = load_accounts()
# Names should describe the thing, not claim a container
# type you're not certain will stay true.
accounts = load_accounts()

Meaningless distinctions happen when two names need to differ only because a language forces uniqueness in scope, not because the underlying concepts differ. Number suffixes (a1, a2) tell a reader nothing about intent - source and destination would say instantly what each argument is for. Noise words are the same problem in a different shape: ProductInfo next to Product doesn't distinguish anything, because "info" doesn't mean anything on its own. If two names can't be told apart without reading the implementation, the names have failed at their one job.

# a1/a2 are noise. So is renaming Product to ProductInfo -
# neither name tells you what's actually different.
def copy_chars(a1, a2):
for i in range(len(a1)):
a2[i] = a1[i]
# source/destination are the actual roles being played.
def copy_chars(source, destination):
for i in range(len(source)):
destination[i] = source[i]

Make names pronounceable and searchable

Code is a social activity - it gets discussed out loud, in reviews, in stand-ups, in pairing sessions. A name you can't say without sounding ridiculous is a name that quietly discourages discussion. genymdhms (generation year-month-day-hour-minute-second) forces everyone who mentions it into an awkward, made-up pronunciation. generation_timestamp needs no explanation and no funny voice.

class DtaRcrd102:
def __init__(self):
self.genymdhms = None # nobody can say this out loud
self.modymdhms = None
self.pszqint = "102"
class Customer:
def __init__(self):
self.generation_timestamp = None
self.modification_timestamp = None
self.record_id = "102"

Single letters and bare numeric constants share a related problem: they're nearly impossible to search for. Grepping for MAX_CLASSES_PER_STUDENT finds every use immediately; grepping for the digit 4 finds every unrelated 4 in the codebase too. The rule of thumb is that the length of a name should match the size of its scope - a loop counter named i inside five lines is fine, but any value with more than a trivial lifetime deserves a name specific enough to find.

# Try grepping for the number 34 or 4 across a codebase.
for j in range(34):
total += (schedule[j] * 4) // 5
WORK_DAYS_PER_WEEK = 5
REAL_DAYS_PER_IDEAL_DAY = 4
NUMBER_OF_TASKS = 34
 
total = 0
for j in range(NUMBER_OF_TASKS):
real_task_days = task_estimate[j] * REAL_DAYS_PER_IDEAL_DAY
total += real_task_days // WORK_DAYS_PER_WEEK

Avoid encodings and mental mapping

Older languages forced type or scope information into names because the language itself couldn't track types - Hungarian notation, m_ prefixes for members, an I prefix for interfaces. Modern languages, IDEs, and type systems already track this information, so encoding it into the name is pure overhead: it makes renaming harder, makes the name uglier, and eventually goes stale when the type changes but nobody updates the name that promised it.

class Part:
def __init__(self, name):
# m_ prefix and a Hungarian-style type hint baked into the name
self.m_dsc_str = name
class Part:
def __init__(self, description):
self.description = description

A related trap is mental mapping - naming something r because you personally remember it's "the lowercased URL with the scheme stripped." That's not cleverness, it's a tax on every future reader (including you, in six months) who now has to reconstruct your private convention before they can read the line at all. Single-letter names are acceptable only for very short-lived loop counters where tradition (i, j, k) already carries the meaning.

Classes, methods, and picking one word

Classes and objects should be named with nouns or noun phrases - Customer, Account, AddressParser - never verbs, and never vague catch-alls like Manager, Processor, or Data that describe nothing about what the class actually holds. Methods, in the opposite direction, should read as verbs - postPayment, deletePage, save - with accessors and predicates following a consistent get/set/is convention so a reader never has to guess whether something is an action or a question.

Interfaces deserve plain names too - ShapeFactory, not IShapeFactory. Callers should just see "a ShapeFactory," not be reminded on every use that they're holding an interface. If one of the two needs an ugly marker, put it on the concrete class (ShapeFactoryImpl) instead. And when a class needs several ways to construct it, named static factory methods

  • Complex.from_real_number(23.0) - describe what each one builds far better than a pile of overloaded constructors ever could.

Once a class and method vocabulary exists, stay consistent with it. Don't let fetch, retrieve, and get coexist as different names for the same operation across different classes - pick one word per concept and use it everywhere that concept appears. The opposite mistake, using the same word for two different concepts (calling both "add two numbers" and "insert into a collection" add), is a pun, and puns in code cost a reader time they didn't need to spend.

Cleverness is its own trap here too. A function named HolyHandGrenade might be funny to the person who wrote it, but DeleteItems tells the next reader what actually happens. Say what you mean; mean what you say.

Solution domain vs. problem domain

Readers of your code are programmers, so computer-science vocabulary - algorithm names, pattern names, data-structure terms - is fair game and often the clearest choice available. JobQueue or AccountVisitor communicate immediately to anyone who knows a queue or the Visitor pattern. But when there's no clean technical term for what you're modeling, reach for the vocabulary of the problem itself, so a maintainer can go ask a domain expert what a name means instead of reverse-engineering it from code.

Add context, but only as much as it needs

Very few names are self-explanatory in isolation. first_name, last_name, state, and zip_code read clearly as a group, but state used alone, three functions away from that group, tells the reader nothing about what it's short for. The cheapest fix is a shared prefix; the better fix is a class - wrapping the related fields in an Address gives the compiler, not just the reader, a reason to know they belong together.

The same problem shows up inside a single function: three related string variables built up across a long conditional chain, with no name tying them together, force the reader to infer their shared purpose by tracing the whole function. Once you notice that shared purpose, giving it a name - even as a small class - both documents the relationship and gives you a natural place to split the logic further.

# number, verb, and pluralModifier only make sense together,
# and nothing in the code says so.
def print_guess_statistics(candidate, count):
if count == 0:
number, verb, modifier = "no", "are", "s"
elif count == 1:
number, verb, modifier = "1", "is", ""
else:
number, verb, modifier = str(count), "are", "s"
print(f"There {verb} {number} {candidate}{modifier}")
# Wrapping the three fields in their own small class gives
# them a shared home - and space to split the logic further.
class GuessStatisticsMessage:
def make(self, candidate, count):
self._set_parts_for(count)
return f"There {self.verb} {self.number} {candidate}{self.modifier}"
 
def _set_parts_for(self, count):
if count == 0:
self.number, self.verb, self.modifier = "no", "are", "s"
elif count == 1:
self.number, self.verb, self.modifier = "1", "is", ""
else:
self.number, self.verb, self.modifier = str(count), "are", "s"

The opposite failure is gratuitous context: prefixing every class in an application called "Gas Station Deluxe" with GSD doesn't add clarity, it adds noise that fights autocomplete and makes every name ten characters longer than it needs to be. Add exactly as much context as a name needs to be unambiguous, and no more.

The real difficulty

None of these rules is mechanical. Choosing a name well requires understanding what a thing actually is and does, which is a design skill, not a vocabulary trick. That's also why renaming shouldn't be scary - a name that turns out to be wrong once you understand the code better isn't a failure, it's information, and modern tooling makes fixing it cheap. The names you end up with are one of the main things a reader has to go on, so it's worth treating every one of them as a small, deliberate decision.