CS 70

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 studentCount
calculateGrade()
Class Data Members (Private) camelCase_
(with trailing underscore)
size_
currentCapacity_
Class Names PascalCase StudentRecord
BinarySearchTree
Constants ALL_CAPS_WITH_UNDERSCORES MAX_BUFFER_SIZE
PI
  • Duck speaking

    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, k for loop indices (especially nested loops)
  • n for a count or size
  • x, y, z for coordinates
  • e for an exception in a catch block
  • T for 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

  1. Numbered Variables: student1, student2 → use an array or std::vector
  2. Hungarian Notation: strName, iCount → the type system handles these
  3. Meaningless Distinctions: ProductInfo vs. ProductData → pick one
  4. “Cute” Names: whack() instead of kill() → be professional
  5. Mental Gymnastics Required: xsq for “x squared” → just use xSquared

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.)