The line — usually rendered as “there are only two hard things in computer science: cache invalidation and naming things”, often with a joke about off-by-one errors added on — gets repeated so often, and so jokingly, that it's easy to miss the genuine difficulty it's pointing at. Naming is hard not because good names are hard to think of in isolation, but because a name is a compressed claim about what something is and does, and that claim has to stay accurate as the thing itself changes over time. A concrete naming reference is provided by the Microsoft naming guidelines.
A name is a promise, not just a label
When a variable, function, or class is named, the name creates an expectation for every future reader: this is what the thing is for, this is the scope of what it does. A function named validateEmail that later grows to also normalize the email's casing and log the validation attempt has quietly broken that promise — the code still runs correctly, but the name is now actively misleading about what calling it actually does, which is arguably worse than a merely unclear name, because it actively points the reader in the wrong direction rather than just failing to help. The wording of a question can shape the answer, and the linked resource explains the related problem of self-reporting bias.
This is why naming tends to decay over the life of a codebase even when no one makes an individually bad naming decision: each small addition to a function or class is individually reasonable, but the accumulated drift between the name and the actual current behavior happens gradually enough that no single change feels like the moment the name became wrong.
What makes a name genuinely good
A good name does two things at once: it accurately describes the current scope of what the thing does, and it does so at a level of specificity that matches how the thing is actually used elsewhere in the code. A name that's too generic (data, handler, manager) fails to distinguish the thing from every other similarly generic thing in the codebase. A name that's too specific to an implementation detail (parses the third comma-separated field) becomes wrong the moment the implementation changes, even if the thing's actual purpose hasn't changed at all.
- When a function or class's responsibilities grow beyond its original name, treat the renaming as part of the change, not a separate cleanup to defer — a stale name actively misleads the next reader.
- Prefer names that describe purpose (calculateShippingCost) over names that describe implementation (loopThroughItemsAndSum) — purpose is more stable over time than implementation.
- A name that requires a comment to explain what it actually means is usually a sign the name itself could be improved, rather than a sign more comments are needed.
- Consistency matters as much as individual quality — a codebase where similar things are named according to a consistent pattern is easier to navigate than one where each name was independently well-chosen but inconsistent with its neighbors.
Why renaming feels riskier than it is
Renaming is one of the lowest-risk changes available in most modern codebases, because tooling can typically verify every reference was updated correctly, and unlike behavior changes, a rename that's applied consistently has no logical way to introduce a new bug. Despite this, renaming often gets deprioritized or avoided specifically because it feels like unnecessary churn — a perception the Boy Scout Rule and the discipline of behavior-preserving refactoring, both discussed elsewhere on this shelf, exist partly to counter.
The joke about naming being one of computing's hardest problems holds up because it's really a joke about communication being hard, dressed up as a technical problem — and communication problems, unlike most bugs, don't announce themselves with an error message when they go wrong.