Chapter 13: Naming as a Spiritual Practice
“The beginning of wisdom is the definition of terms.”
— Socrates
A bad name can ship a bug.
There’s a joke in our industry that naming things is one of the two hardest problems in computer science. It’s funny because it’s true. And it’s true because nobody can quite explain why.
Naming should be easy. You have a thing. You give it a word. The word refers to the thing. Children do this before they can walk. And yet here we are, senior engineers with decades of experience, staring at a blinking cursor trying to decide whether it’s a UserManager or a UserService or a UserHandler or something else entirely. We’ve all written AbstractFactoryManagerFactorySingleton at some point, or something equally absurd, and felt the shame of knowing we’d failed at the most basic act of communication.
I don’t have a clean answer for why this is hard. If I did, I’d be sipping tea on a mountainside instead of writing this book. But I’ve come to believe that the difficulty isn’t really about words. It’s about understanding. And understanding, in our line of work, is something we often don’t have until we’re almost done.
The Trap
I once worked in a system that used a homegrown framework for data persistence. Models had a save() method that took the changes you’d made and returned a new immutable record. That record then had a persist() method that actually wrote to the database. In other words: save() updated an in-memory snapshot; persist() performed the actual write.
The bugs were constant. Developer after developer assumed save() saved to the database. They’d call it, see no errors, move on. The data never persisted. Features would work in tests and fail in production. We’d trace through the code, find the missing persist() call, add it, and move on to the next fire.
The names were lying. save() sounds like it saves. Every other framework in the ecosystem uses save() to mean “write to the database.” The developers who chose these names had a mental model that made sense to them, but they’d violated every expectation that anyone else would bring to the code. The name created a trap, and we fell into it over and over.
This is the damage bad names cause. Not just confusion, but actual defects. Bugs that flow from words. Systems that fail because someone trusted a name to mean what it should have meant.
A name is a promise to every future reader. When the promise is false, the bugs begin.
The Fog
Why does this happen? Why do smart people choose names that mislead?
What we do is similar to sailing in the 1400s. We set off into unknown problem space, knowing that somewhere on the other side of the fog is a place where the solution lives. We think we know the way. We have intuitions, hunches, architectural diagrams sketched on whiteboards. But we can only say in retrospect whether we took the right path, or whether we reached the right destination through a suboptimal route.
Our job, almost by definition, is working in spaces we don’t fully understand. We figure it out as we go. We meander. We double back. We refactor, as Chapter 10 described, because understanding deepens and the code needs to reflect what we now know.
At the end of this journey, naming is usually easy. The concepts have crystallized. The boundaries are clear. You know what each piece does because you’ve lived with it long enough to feel its shape. The name appears, obvious in retrospect, and you wonder why it took so long to see it.
It’s the middle that’s hard. When you don’t know exactly where you’re going. When the picture hasn’t set in your mental space yet. When you’re still exploring, still guessing, still hoping the fog will lift. That’s when naming feels like sandpaper on the brain.
Naming is the moment you stop pretending you’re building “something” and admit what you’re building. It requires a kind of honesty that’s hard to summon when you’re still figuring things out.
Understanding First
You cannot properly name something you do not understand.
This sounds obvious, but it explains most of the friction. We try to name things too early. We’re building the system, we need to call it something, so we grab the first word that seems close enough and move on. Then the understanding deepens, the name no longer fits, but the name is everywhere now, in files and functions and API calls and documentation. The wrong name becomes load-bearing. Changing it is expensive. So we live with it, and the mismatch between name and reality becomes a source of constant low-grade confusion.
The discipline is patience. When you don’t understand something well enough to name it, admit that. Use a placeholder. Call it something obviously provisional. Ugly names are honest. They signal that the understanding isn’t there yet. They invite future revision. The only rule is that provisional names must be treated like TODOs, not like architecture. A confident but wrong name is far more dangerous, because it pretends to knowledge you don’t have.
Some of the worst code I’ve encountered was impeccably named, every class and method labeled with professional-sounding terms that bore no relationship to what the code actually did. The names were a mask. They made the code look organized while hiding its chaos. The developers had named things before they understood them, and the names had calcified into lies.
Revelation
I used to think finding the right name was like solving a puzzle. You had the thing, you searched for the word, you found it. The name unlocked the code.
I don’t think that anymore. Now I believe it works the other way. The code falling into place reveals the name.
When I look back at the moments when a name finally felt right, it wasn’t because I’d had a flash of linguistic insight. It was because the refactoring had finished, or the abstraction had clarified, or the boundaries had shifted until the thing I was naming had become something I actually understood. The name appeared because there was finally something clear enough to name.
This is why naming and refactoring are so deeply connected. Chapter 10 talked about returning to code with new understanding. Naming is often the final act of that return. You’ve reshaped the code until it reflects what you know. Now you can call it what it is.
The sculptor doesn’t name the statue before carving it. The name comes when the form emerges from the stone.
Context Shapes Everything
A name doesn’t exist in isolation. It exists in a context: the object it belongs to, the module it lives in, the conventions of the codebase, the conversations of the team.
process() is a terrible name in isolation. Process what? How? But paymentGateway.process(transaction) is perfectly clear. The object provides the context. The name only needs to specify what isn’t already obvious.
Watch for weasel words that hide confusion. When you name something UserManager or DataHandler or ServiceUtils, you’re often admitting you don’t know what it does. “Manage” and “handle” and “util” are placeholders dressed in professional clothing. They sound precise while meaning nothing.
This is why specificity is generally better, but not always. validateAndSaveUser() is more specific than process(), and usually that’s an improvement. But if the function lives on a UserValidator object that clearly handles user validation, then validateAndSave() might be enough. You can’t decide without seeing where the name will be used.
The same applies to how you speak about code. Names that feel awkward in conversation often feel awkward in code. If you find yourself constantly clarifying what a name means when you talk about it, that’s a signal. The name isn’t doing its job. It’s creating work instead of removing it.
Consistency Over Perfection
There’s a temptation to find the “perfect” name. The word that captures exactly what the thing is, with precision and elegance.
Resist it.
Consistency matters more than perfection. Your team has conventions. Your codebase has patterns. Those conventions create a shared vocabulary, a playground with boundaries that everyone understands. The perfect name that violates those conventions is worse than the good-enough name that fits.
Chapter 11 talked about boring code. Boring names are often the best names. They don’t demand attention. They don’t surprise anyone. They do what every other name in the codebase does: communicate clearly within the established patterns.
When you’re tempted to invent a new naming convention because the existing one doesn’t quite fit your use case, pause. The cognitive cost of inconsistency almost always outweighs the benefit of precision. Find a name that works within the constraints. Save your creativity for the problems that actually need it.
The Machine Reads Too
Names matter to AI for the same reason they matter to humans: clarity. But the mechanism is different, and understanding it changes how you think about naming.
AI reads code as tokens and predicts what comes next based on patterns. When you name a function getUser, the model expects retrieval. When you name it createUser, the model expects creation. If getUser actually creates a user when one doesn’t exist, the AI will write code that assumes it doesn’t. The name shaped its predictions, and the predictions were wrong.
This isn’t anthropomorphizing. It’s how the technology works. Names are the semantic anchors that AI uses to build its model of your code. Misleading names don’t just confuse humans; they systematically corrupt every suggestion, every completion, every generated test.
When you name things accurately, you’re aligning your codebase with the probability distributions that AI was trained on. When you name things poorly, you’re fighting against every pattern the model has ever learned. Good naming isn’t just documentation. It’s the interface between your intent and every automated system that will ever touch your code.
The Practice
I called this chapter “Naming as a Spiritual Practice” partly as a joke, but there’s something to it.
When I find the right name, it doesn’t feel like I chose it. It feels like it revealed itself. Like I’d been circling around something I couldn’t see, and then the fog lifted, and there it was. Obvious. Inevitable. Already true before I wrote it down.
That feeling, the moment of alignment between your understanding and the code, is what I mean by spiritual. Not mystical, not religious, just… aligned. A moment when things fit. When the struggle of naming dissolves because you finally understand what you’re naming.
You can’t force that moment. You can only create the conditions for it. Write code. Refactor. Let understanding deepen. Pay attention to when names feel wrong, and treat that discomfort as information. The friction is telling you something. Usually it’s telling you that you don’t understand the thing well enough yet.
The name will come. It always does. Not because you’re clever with words, but because you’ve done the work to understand what you’re building.
That’s the practice. Not finding the perfect word. Understanding the thing deeply enough that the word finds you.