Naming Functions and Methods
A function name is a contract. It promises what the function does. When the name lies, bugs follow.
I once debugged a function called getUser that, deep in its implementation, also sent a welcome email to new users. The name said "get." The behavior said "get and send email and log analytics."
That's a lie. Lies in code cost time and trust.
Verbs That Describe Behavior
The verb in a function name should accurately describe what happens.
Fetching Data
| Verb | Meaning | Implication |
|---|---|---|
get | Return existing value | Synchronous, from memory, no side effects |
fetch | Retrieve from external source | Async, network call, might fail |
load | Bring into memory | Might read from disk, affects state |
find | Search for something | Might return nothing (null/undefined) |
query | Database operation | SQL or similar, returns results |
read | Read from storage | File, database, stream |
Using these consistently means readers can predict behavior from the name:
getUser() // Returns cached user, fast, never fails
fetchUser() // Hits API, might throw, needs await
findUser() // Might return undefined if not found
loadUserData() // Reads from disk, populates state
Modifying Data
| Verb | Meaning | Implication |
|---|---|---|
set | Assign value | Usually local state, synchronous |
save | Persist to storage | Database, file, external system |
update | Modify existing | Assumes thing exists |
create | Make new thing | Assumes thing doesn't exist |
delete | Remove thing | Permanent removal |
remove | Take out of collection | Might not be permanent |
add | Put into collection | Append, insert |
Transforming Data
| Verb | Meaning | Implication |
|---|---|---|
to | Convert format | toString(), toJson(), toDate() |
parse | Extract structure from text | parseJson(), parseUrl() |
format | Convert to display string | formatDate(), formatCurrency() |
calculate | Compute from inputs | calculateTotal(), calculateTax() |
build | Construct complex object | buildQuery(), buildResponse() |
normalize | Standardize format | normalizeEmail(), normalizePhone() |
validate | Check correctness | validateEmail(), validateInput() |
Naming Side Effects Honestly
This is critical: if a function has side effects, the name must say so.
Bad: Hidden Side Effects
function getUser(id) {
const user = cache.get(id) || db.fetch(id);
analytics.track('user_accessed', { id }); // Hidden!
if (!user.welcomeEmailSent) {
email.sendWelcome(user); // Hidden!
}
return user;
}
The name says "get." The function does three more things. Every caller is surprised.
Good: Honest Names
Option 1: Split the functions
function getUser(id) {
return cache.get(id) || db.fetch(id);
}
function trackUserAccess(user) {
analytics.track('user_accessed', { id: user.id });
}
function sendWelcomeEmailIfNeeded(user) {
if (!user.welcomeEmailSent) {
email.sendWelcome(user);
}
}
Option 2: Name the combination honestly
function getUserAndTrackAccess(id) {
const user = getUser(id);
trackUserAccess(user);
return user;
}
The name now tells the truth.
Command-Query Separation (CQS)
Bertrand Meyer's principle: a method should either do something (command) or return something (query), but not both.
Commands
Commands change state. They're usually named with imperative verbs:
saveOrder(order)
deleteUser(userId)
updateInventory(productId, quantity)
sendEmail(to, subject, body)
Commands can return success/failure status, but shouldn't return data.
Queries
Queries return data. They don't change state:
getOrderById(orderId)
findUsersByRole(role)
calculateTotalPrice(items)
isUserActive(userId)
Queries are safe to call multiple times with no side effects.
Why This Matters
When you follow CQS, readers know:
getUser(id)— safe, no side effects, can call freelydeleteUser(id)— dangerous, has permanent effect, call carefully
When you violate CQS:
getUser(id)— might do anything, have to read implementation
Practical Exception
Sometimes combining is pragmatic:
// Returns the new state after mutation
const newCount = incrementAndGet(counter);
// Returns created entity with generated ID
const savedUser = createUser(userData);
These are acceptable when:
- The side effect and return value are tightly related
- The name makes the combination clear
- Separating them would be awkward
Function Length and Name Complexity
A function that does one thing can have a short name.
A function that does many things needs a long name—which is a smell telling you to split it.
// Doing too much, name tries to compensate
function validateParseAndStoreUserInputWithEmailNotification(input) {
// ...
}
// Split into focused functions
function validateInput(input) { ... }
function parseUserData(validInput) { ... }
function storeUser(userData) { ... }
function notifyUserCreated(user) { ... }
If you can't name a function simply, it's probably doing too much.
Symmetrical Names
Related functions should have related names:
| Operation | Opposite |
|---|---|
open | close |
start | stop |
begin | end |
create | destroy |
add | remove |
insert | delete |
show | hide |
enable | disable |
acquire | release |
lock | unlock |
If you have openConnection, the cleanup should be closeConnection, not terminateConnection or endConnection.
Key insight: Function names are promises. Use verbs that accurately describe behavior. Be honest about side effects. Follow command-query separation so readers can predict what functions do without reading their implementation.