The Art of Choosing Good Names
The Mechanical Rules (Style Conventions)
First, let's establish our consistent style conventions.
| Thing | Format | Examples |
|---|---|---|
| Local Variables and Functions | camelCase |
studentCountcalculateGrade() |
| Class Data Members (Private) | camelCase_(with trailing underscore) |
size_currentCapacity_ |
| Class Names | PascalCase |
StudentRecordBinarySearchTree |
| Constants | ALL_CAPS_WITH_UNDERSCORES |
MAX_BUFFER_SIZEPI |
Why the underscore at the end of private data members?
Rationale for Trailing Underscore for Private Data Members
The trailing underscore is a common convention in C++ that helps distinguish private data members from other variables, especially in constructors and member functions where parameter names might be similar to member names. For example, consider this snippet from the middle of some member function:
if (numStudents > roomCapacity_) {
requestSeats(std::max(numStudents, roomCapacity_ * 2));
}
Even without knowing the broader context, it's clear that roomCapacity_ is a member variable, while numStudents is likely a local variable or parameter, making the code easier to read.
An Exception to the Trailing Underscore Rule
The trailing underscore is only for private data members. Typically, in a well-designed class, all the data members are private, but in rare cases there might be exceptions to this rule. If you have a struct or a class with public data members (e.g., a simple data container), you should not use the trailing underscore for those public members.
struct Coord {
int x; // No underscore, because it's public
int y;
};
In this case, Coord has no member functions, so we will never be writing just x or y alone, we'll always be saying myCoord.x or someOtherCoord.y, so there's no risk of confusion and, perhaps more importantly, the names x and y are part of the public interface of the Coord type.
If you have a struct that is part of the internal implementation of a class, making an exception to the trailing underscore rule is less compelling. To keep the rules simple, we only drop the trailing underscore for public data members that are part of the public interface of a type. In most assignments, public interfaces are specified by the assignment, and you'll be concerned with private implementation details, so every time you are choosing a name for a data member, it should have a trailing underscore.
The Art of Good Naming
Principle 1: Match Name Length to Scope
The broader the scope, the more descriptive the name should be.
// GOOD: Short name for short scope
for (size_t i = 0; i < values.size(); ++i) {
std::cout << values[i] << std::endl;
}
// GOOD: Descriptive name for broader scope
class GradeCalculator {
private:
double homeworkWeight_; // Clear across entire class
double examWeight_;
};
// BAD: Too verbose for tiny scope
for (size_t indexIntoTheValuesArray = 0; indexIntoTheValuesArray < values.size(); ++indexIntoTheValuesArray) {
std::cout << values[indexIntoTheValuesArray] << std::endl;
}
// BAD: Too cryptic for broad scope
double hw_; // What's "hw"? Seen across entire class
Principle 2: Names Should Express Intent, Not Implementation
Tell the reader what something represents, not how it's stored.
// GOOD: Says what it represents
size_t numStudentsEnrolled;
bool isValidInput;
// BAD: Describes the type, not the purpose
int intValue;
std::vector<string> stringVector;
// Exception: When the type IS the point (e.g., in generic code)
template<typename T>
void swap(T& first, T& second); // "first" and "second" are fine here
Principle 3: Use Domain Conventions Where They Exist
Some single letters have strong conventional meanings:
i,j,kfor loop indices (especially nested loops)nfor a count or sizex,y,zfor coordinatesefor an exception in a catch blockTfor a template type parameter
// GOOD: Using conventions
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
matrix[i][j] = 0;
}
}
// UNNECESSARY: Fighting convention
for (size_t rowIndex = 0; rowIndex < rows; ++rowIndex) {
for (size_t colIndex = 0; colIndex < cols; ++colIndex) {
matrix[rowIndex][colIndex] = 0;
}
}
Principle 4: Be Precise, Not Just Descriptive
Avoid vague words such as “data”, “info”,“temp”, or “result” without qualification.
// VAGUE
string data;
int result;
double temp;
// PRECISE
string studentName;
int examScore;
double temporarySum; // or just "sum" if the temporary nature is obvious
Principle 5: Avoid Ambiguity
Names should have one clear interpretation.
// AMBIGUOUS: Is filter keeping or removing the premium users?
std::vector<User> filterPremiumUsers(std::vector<User> users);
// CLEAR
std::vector<User> keepOnlyPremiumUsers(std::vector<User> users);
std::vector<User> removePremiumUsers(std::vector<User> users);
// AMBIGUOUS: Number of students? Student ID number?
int studentNumber;
// CLEAR
int studentCount;
int studentId;
Principle 6: Use Searchable Names
Single letters and numbers are hard to search for in code.
// HARD TO SEARCH: What does 7 mean?
if (daysSinceLastLogin > 7) {
markAsInactive();
}
// SEARCHABLE
constexpr int DAYS_UNTIL_INACTIVE = 7;
if (daysSinceLastLogin > DAYS_UNTIL_INACTIVE) {
markAsInactive();
}
Principle 7: Don't Use Mental Mapping
Readers shouldn't have to mentally translate your names.
// BAD: Reader must remember "d" means days
int d = getDaysSinceModified();
// GOOD: Self-documenting
int daysSinceModified = getDaysSinceModified();
Exception: If we used d in the very next line and then we didn't use it again, Principle 1 would apply and it would be fine to use a short name.
Principle 8: Consistency Trumps Personal Preference
If the codebase uses size for container sizes, don't switch to length or count.
// If the codebase uses this pattern:
size_t getSize() const;
// Don't introduce:
size_t getLength() const; // Same concept, different name
Common Naming Patterns
Boolean Names
Start with is, has, can, or similar; for example,
isEmpty,hasChildren,canWrite,shouldUpdate
Function Names
Use strong verbs for actions, nouns/adjectives for queries.
- Actions:
calculateTotal(),sendEmail(),validateInput() - Queries:
size(),isEmpty(),student()
Paired Concepts
Use symmetric names.
begin/end,first/last,open/close,show/hide- Not
begin/last,open/destroy
Red Flags to Avoid
- Numbered Variables:
student1,student2→ use an array orstd::vector - Hungarian Notation:
strName,iCount→ the type system handles these - Meaningless Distinctions:
ProductInfovs.ProductData→ pick one - “Cute” Names:
whack()instead ofkill()→ be professional - Mental Gymnastics Required:
xsqfor “x squared” → just usexSquared
The Golden Rule
A good name allows a reader to understand the code's purpose without reading its implementation.
When in doubt, imagine you're explaining your code to a classmate over the phone. The names you'd naturally use in that explanation are probably the right ones for your code.
(When logged in, completion status appears here.)