description | last_modified |
---|---|
Some ideas on finding good names |
2022-01-27 17:10:02 UTC |
- Intention-revealing names
- Meaningful distinctions
- Consistency
- Searchable names
- Avoiding overly long names
- Solution domain names
- Implementer versus user names
- Names and design
- Names and legacy code
- Resources
Name should be explicit about what something is supposed to be, without requiring additional comments
Bad:
int elapsed; // elapsed time in seconds
int created; // days since creation
Good:
int elapsedTimeSeconds;
int daysSinceCreation;
When dealing with similar but different things, go for names that make it clear which is which
Bad:
function canAddCustomer(customer) {
for (const cust of customerService.getCustomers()) {
if (customer.name === cust.name) {
return false;
}
}
return true;
}
Good:
function canAddCustomer(newCustomer) {
for (const existingCustomer of customerService.getCustomers()) {
if (newCustomer.name === existingCustomer.name) {
return false;
}
}
return true;
}
Pick one word per concept and use that consistently
Example: "fetch" vs. "get" vs. "retrieve"
- Don't use different ones for code that is basically doing the same thing
- If different ones mean different things (for example, "get" for already retrieved values and "fetch" when retrieving from DB), be consistent about it
For public constants or other things used in a wider scope, use specific names that make them easy to search for if needed
Rule of thumb: "length of name should correspond to its scope"
- Distract from the actual operations that are being performed
- Hard to scan visually
- Especially bad if there are subtle differences between similar names
- Force extra line breaks which interrupt the flow of the code
- Long class names discourage developers from declaring variables of that type
- Long method names distract from their argument lists
- Long variable names obfuscate the logic
- Self-documenting code is good, but don't take it too far by including everything you know into the name
- Instead of
checkIfCityIsKnownAndStreetIsInCity
, go forverifyAddress
- Instead of
- A somewhat long function with a few comments indicating logical blocks could be a lot more readable and maintainable than a bunch of small functions with overly long and specific names
- The harder it is to come up with a short and clear name, the more likely it is that you're taking the splitting too far
- The need to go with a very long name can indicate that the function you've split off just doesn't make too much sense on its own
Bad:
String nameString;
List<Employee> employeeList;
Good:
String name;
List<Employee> employees;
Code with unnecessary context:
class UserService {
constructor(private userRepository: UserRepository) {}
getUserByUserId(userId: number) {
return this.userRepository.getUserByUserId(userId);
}
}
Unnecessary context:
- Since we're in
UserService
, it's obvious that we are getting users, the repository is a user repository and provided IDs will be user IDs unless stated otherwise - Same for
UserRepository
Improved version:
class UserService {
constructor(private repository: UserRepository) {}
getById(id: number) {
return this.repository.getById(id);
}
}
Note: sometimes, additional context is necessary!
- Inside an
Address
class, it's clear whatstate
means - Used separately in another context, a variable named
state
could contain anything (especially since "state" is a generic term in software development), soaddressState
would be better
Once a name makes it clear what it refers to and what it doesn't refer to, any additional info is redundant
Examples:
superStrongFinalBoss
doesn't add more value thanfinalBoss
if there's only one final boss or if all final bosses are super strongPayingCustomer
doesn't add more value thanCustomer
if all customers are paying
Some generic words that often don't mean anything when added to a name:
- Data
- State
- Amount
- Value
- Manager
- Engine
- Object
- Entity
- Instance
Rule of thumb: does the name still mean the same if you remove the generic word?
- If yes, remove it
- If no, keep it (or try to find a better name)
Function names using "and" or "or" to combine parts typically mean the function is doing too much
Ways to improve this:
- Break apart into smaller pieces
- If the two things should really always happen together:
- Consider finding a better name for that entire operation
- Consider creating something else that encapsulates the combination of the smaller pieces
Exceptions:
- "getXOrThrow" can be a very useful pattern to indicate a function that will throw when it can't find what it's looking for
Use solution domain names (algorithms, design patterns) where it makes sense, since those are obvious to other developers familiar with them
Examples:
- OrderObserver
- JobQueue
Note: Not all developers are familiar with all algorithms/patterns!
- "AccountVisitor" might be confusing to developers not familiar with Visitor pattern
- Potential solutions:
- Look for another name (although this might confuse developers already familiar with the pattern)
- Look for a simpler solution without the complexity from the pattern
- Educate the team on useful patterns for your codebase
Prefer names that make sense in the context of client code over names that describe implementation (in this context, user = developer writing client code)
See also Client-first design
Example from C++: log2p1()
- What it does: return binary logarithm of the input value, plus one
- Name is exactly this
- Why it's useful: result is the number of bits needed to store the input value
- Why the name is bad:
- Name is only obvious to someone who already knows how number of bits is calculated
- Anyone looking at the code using it will have to make the same connection
- Better name:
bit_width()
- Obviously relevant to developer wanting to calculate required number of bits
- Immediately clear when looking at code using it
Example of an exception to this: popcount()
- What it does: execute the
popcount
instruction - Why it's useful: counts the number of bits set to 1
- Why the name seems bad:
- The "pop" has nothing to do with "push", it's an abbreviation for "population"
- Even "population count" doesn't mean a lot to someone not familiar with it
- Why the name is actually good: "popcount" is the standard name for a function doing this, so anyone familiar with bit manipulation will be looking for this name
- Bad names make it harder to build a mental model of the code, which also makes it harder to create a good design
- If it's hard to name something, this can be an indicator that the thing you're trying to name is not designed well
- Could be taking on too many responsibilities
- Could be missing a crucial part
When dealing with legacy code, it can make sense to temporarily go with "bad" names that are overly long and almost describe the entire implementation. This way, it's at least clear what a piece of code is doing. Then, when you're more confident, you can start actually improving the design and improving names in the process.
While you're trying to make sense of a codebase, get_response_id_and_add_response_to_db
can be a helpful name. Or even get_response_id_and_probably_use_db
if you haven't been able to fully untangle the function's implementation yet.
See also Is it fine to use "AND" in a function name?
- Clean Code (book by Robert C. Martin) (summary of relevant chapter)
- It's probably time to stop recommending Clean Code (relevant comment about long names)
- Self Documenting Code
- Long Names Are Long
- Naming Things: Implementer vs. User Names
- Naming Things in Code
- Is it fine to use "AND" in a function name?