The name of a function matters more than how it works.
Not in an Uncle Bob clean-code-gospel way. Most of my work now goes through Claude Code, and that changed how I think about this. Claude reads your function names and trusts them. A human will open the function and check. Claude sees updateStatusAndSendEmail, takes it at face value, and refactors under that assumption. If the name lies, you’re debugging the AI’s mistakes on top of your own.
The problem with honest names
Most of the code I’ve inherited names functions after how they work. updateStatusAndSendEmail(), getUserByIdAndCheckPermissions(), writeToDBAndPublishEvent(). These names tell you exactly what happens behind the scenes. Sounds useful, right?
It’s not. It’s a trap.
When you name a function after its implementation, you lock the interface to the internals. Six months later, someone adds a Slack notification to updateStatusAndSendEmail(), and the name is now wrong. No one renames it because twenty other functions call it. So you end up with a function called updateStatusAndSendEmail that also pings Slack and writes an audit log. The next person has to open the function to understand what it actually does.
I’d rather it had been called approveApplication() from the start. The internals can change. The intent stays the same.
This is subjective, by the way
This is how I see it. Not everyone agrees, and I’ve worked with people who think implementation-named functions are more honest. Fair enough. When everything is named for intent, you sometimes have to dig to understand what actually happens at runtime. processOrder() could be doing anything.
But I’d rather read code that tells me what the system is trying to do, and only look at the details when I need to. Reading code that spells out every database call and makes me reverse-engineer the business logic? I find that worse. I get the other side, though.
Where it actually matters
I write TypeScript for the backend. This is where naming for intent pays off.
Route handlers and service functions. I used to name handlers after the HTTP method or the database operation. Those names don’t help anyone. Every handler responds to a request. The name should tell you the business action.
- ❌
postUser→ ✅approveApplication - ❌
handleRequest→ ✅expireStaleInvitations - ❌
processPayload→ ✅syncCustomerToSearch
When you have a service with thirty handlers, the gap between handleRequest and expireStaleInvitations is the difference between wanting to open the file or not.
Repository and query methods. I’ve seen repositories full of query names that are technically correct but useless. The name should tell you the business question the query answers, not which columns it filters.
- ❌
getById()→ ✅findActiveCustomer() - ❌
getByField()→ ✅findOverdueInvoices() - ❌
getByMultipleFields()→ ✅findApplicationsByStatus()
The database call is the same either way. The name tells you different things.
Events and messages. Whether you’re using a message queue, pub/sub, or webhooks, name the event after what happened in the domain, not what triggered it.
- ❌
database.row.updated→ ✅application.approved - ❌
handler.completed→ ✅customer.created
A consumer should be able to decide whether they care about an invoice.overdue event without knowing which database trigger or background job fired it.
Types and interfaces. This one slips past code review a lot. What is a “UserDTO”? Is it data going to the frontend? Coming from the database? Both?
- ❌
IUserData,UserResponse,UserDTO - ✅
CustomerProfile,InvoiceLineItem
Name the shape after what it represents in the domain, not how it gets serialized.
When it backfires
Naming for intent isn’t always the right call. I’ve come up with intent names that were worse than the implementation-based ones.
I once named a function ensureCustomerIsReady(). It sounded great in the PR. But “ready” meant something different in three contexts, and no one could tell from the call site whether it was checking a flag, running validation, or actually changing state. The implementation-named version, validateAndActivateCustomer(), would have been awkward, but at least it wouldn’t have confused anyone.
So: name for intent when the intent is clear and stable. If you can’t describe what the function does in simple terms without it being ambiguous, the implementation name might be better. Sometimes fetchUserAndCheckRole() is just more useful than authorize(), because your system has four different things that could be called “authorize.”
The Luffy test
In One Piece, Luffy describes his crew by their roles: “one is the cook, one is the navigator, one is the doctor.” He doesn’t say “the one who also fights and also has Haki and also found the All Blue.” Every crew member gets one label. Sanji is the cook. The rest is detail.
Your functions should work the same way. What’s this function’s role on the team? Call it that. If you can’t pick one role, it’s probably doing too much.
I don’t have rules for this
I know people want a checklist. “Always do X, never do Y.” I don’t have that. I have a bias: name for intent, switch to implementation when intent is ambiguous. Sometimes I’m wrong and the intent name causes more confusion than it solves. I just find that it’s usually less misleading.
The only test I use: can someone read the call site without opening the function and understand what the code is doing? If yes, the name works. If they have to open it to make sense of the code around it, the name isn’t doing its job.