Naming Things – A Comprehensive Guide


Naming Things

Naming Things is Hard

There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.1

Leon Bambrick

Naming Things is hard. The guidance provided here is derived from years of making mistakes, wishing I had done things better, and finding ways to get tools to do my work for me (and get on with the interesting problems). My hope here is to shortcut the learning for many people so you, too, can get on with more interesting things and leave behind more beautiful (and functional) code.

The Necessity of Good Names

Why are well-chosen names so crucial? Let’s break it down:

1. Providing Metadata:
Names aren’t just placeholders; they encapsulate the essence of the variable, function, or class. A variable named userCount instantly conveys its intent – to hold the number of users – whereas a variable named uc or x lacks clarity. Example:

int userCount = 0;  // Clear intent
int uc = 0;         // What does "uc" stand for?

2. Ensuring Consistency and Clarity:
Consistency in naming conventions bridges the cognitive gap for developers who read your code. If functions related to database operations are prefixed with db, then seeing dbRetrieveUser or dbUpdateRecord brings immediate clarity.

3. Aiding Automation and Tools:
Modern IDEs can assist with tasks like refactoring, but they rely on clear naming conventions. Tools can more reliably find and update references if your project employs a consistent naming pattern.

4. Enhancing Aesthetics and Professionalism:
Names like finalBossLevel or taxCalculation exude professionalism. Conversely, names with inside jokes, memes, or temporary labels (like tempVar123 or thisWillBeChanged) erode confidence in the codebase.

5. Avoiding Naming Collisions:
Especially in large-scale projects or when integrating third-party libraries, unique and descriptive names minimize the risk of duplicate identifiers. Employing namespaces or specific prefixes can help. For instance, if you’re developing a gaming engine, using a prefix like engine_ can prevent collisions.

6. Facilitating Effective Project Handovers:
A well-named codebase is a treasure when transitioning a project between teams or developers. It cuts down onboarding time and reduces the need for extensive documentation. A function named calculateTotalTax is self-explanatory, reducing the time spent hunting through documentation or seeking clarifications.

Attributes of Good Names

Good Names Encode Information

When you encounter a name in code, it should provide a clear hint about its purpose and how it’s used. This implicit encoding of information streamlines comprehension.

For example:

double monthlySalary;  // Implies a single month's salary.
double calculateAnnualSalary(double monthlySalary);  // Function clearly denotes its purpose and parameter.

Reducing Cognitive Load with Good Names

In both computer science and psychology, the term “cognitive load” refers to the mental effort required to process information. In software development, the primary consumers of code are humans – the original authors, their colleagues, and oftentimes, future developers who may be entirely unfamiliar with the original context. Given that code is read more often than it’s written, it is crucial to ensure that this reading process is as frictionless as possible. Descriptive names reduce the time and mental energy needed to understand what a piece of code does.

Consider:

double calculateTax(double price, double rate);  // Direct and clear.

versus a less clear alternative:

double ct(double p, double r);  // Requires more effort to decipher.

Automation and Good Naming

Names that adhere to clear conventions can be utilized by tools to automate processes. Automated tools and scripts can parse and understand names more easily when they follow a recognizable pattern.

For instance, a codebase with functions named getUserName, getUserAge, and getUserAddress allows tools to easily identify functions related to retrieving user information.

Good Names Scale Well

As software projects grow, so does the importance of scalable naming conventions.

Imagine starting a project with:

myProject.prj
myProject_new.prj
myProject_broken.prj
myProject_latest.prj
current_myProject.prj
final_myProject.prj

As the project expands to include more builds and deliveries:

myProject_20231024.prj
myProject_20231025.prj
myProject_20231026.prj.broken
myProject_20231026.prj
myProject_20231027.delivered.prj
myProject_20231028.prj

Using a clear and scalable naming convention that encourages sequence prediction from the start will save countless hours.

Creating a Schema for Naming

In this context, a schema refers to a defined structure or pattern for names. For example:

  • Functions that retrieve data can start with get, such as getEmployeeDetails.
  • Functions that set data can begin with set, like setEmployeeDetails.

Following a schema provides a mental map, making navigation and comprehension faster and more intuitive.

Taxonomic Ranking in Naming

Inspired by the organization seen in biological taxonomy, Taxonomic Ranking in Naming provides an approach to structure and name components in a project in a hierarchical manner, reflecting their relationships and generalization levels.

Why Taxonomic Ranking?
  1. Discoverability: By having consistent base names or prefixes for related items, you enhance IDE autocomplete functionality, making it easier to discover related subclasses or variants by just typing the base name.
  2. Logical Hierarchy: Similar to how biological taxonomy has Kingdom > Phylum > Class > Order > Family > Genus > Species, our naming convention can indicate the lineage and relationship between entities.
  3. Consistency: A defined naming convention ensures a consistent approach when introducing new classes or subclasses.
Example: Simulation Library Using a Taxonomic Hierarchy

Let’s relate the components of a simulation library to the biological taxonomy:

  • Kingdom (most general): Entity
    • Represents any object within the simulation.
  • Phylum (a level down): Aircraft
    • All entities related to aircraft.
  • Class (more specific): Cesna
    • Specific type or model of aircraft. This could be Prop or Jet, or Civilian or Military, depending on how deep you wanted the hierarchy to go.

The final name would be EntityAircraftCesna. (Or possibly EntityAircraftCivilianCesna.)

For marine vehicles, the structure would look something like:

  • Kingdom: Entity
  • Phylum: Ship
  • Class: Cargo

In this approach, the idea is that if a developer is looking for a type of aircraft, typing “Aircraft” into the IDE will present all related classes, providing a logical and structured way to explore the codebase.

Example: File and Directory Structuring

Arrange files and directories in a way that mirrors their hierarchical relationships.

src/
├── graphics/
│   ├── base/
│   │   ├── Shape.cpp
│   │   └── Shape.h
│   ├── triangles/
│   │   ├── TriangleBase.cpp
│   │   ├── TriangleIsosceles.cpp
│   │   └── TriangleEquilateral.cpp
│   └── circles/
│       ├── CircleBase.cpp
│       └── CircleFilled.cpp
Example: Function or Method Hierarchies

If certain functions serve as utilities or extensions for a primary function, their names can reflect this hierarchy.

void draw();
void drawWithTexture();
void drawWithShadow();
Example: Variable Ranking

Sometimes, variables within a system may have a clear ranking or order of importance. Naming them taxonomically can clarify their roles.

double totalAmountMain;
double totalAmountSubTotal;
double totalAmountAdjusted;
Advantages of Taxonomic Ranking
  • Intuitive Organization: Much like a well-organized library, it becomes easier to locate specific items (or, in this case, pieces of code) when they’re organized taxonomically.
  • Scalability: As the codebase grows, new entities can be easily categorized without disrupting the existing structure.
  • Enhanced Collaboration: For teams, a taxonomic approach ensures everyone is on the same page, reducing the chance of miscommunication or misunderstanding.

Employing taxonomic ranking in naming conventions harnesses the power of hierarchical classification to bring order, clarity, and consistency to a codebase. It’s a tool that, when used appropriately, can greatly enhance the quality and maintainability of software projects.

Taxonomic Ranking in Naming offers an insightful and organized method to name classes hierarchically, providing clarity on their relationships and levels of generalization. Whether developing a simulation library, game engine, or another project, this approach can foster a more intuitive and navigable code structure.

General Principles of Good Naming

Navigating through a well-named codebase is akin to reading a well-organized book – it flows, it communicates, and, most importantly, it makes sense. The importance of good naming conventions in software cannot be overstated. Here’s a detailed breakdown of the principles:

Prefer Clarity Over Brevity

While shorter names make for less typing, they often don’t communicate their purpose well. It’s more beneficial for names to be self-explanatory, even if that means a few extra characters.

Example:

int d;                  // Unclear
int daysSinceLastLogin; // Descriptive

Use Active Verbs for Functions

Functions usually perform actions, so their names should reflect this with clear, active verbs. When naming functions in programming, think of them as actors performing specific tasks. As such, using active verbs can effectively convey their primary purpose. Functions usually perform actions; their names should clearly reflect this dynamic nature. By adhering to this guideline, developers can ensure that the function’s intent is immediately evident, making the code more readable and intuitive.

Example:

void calculateTotal();  // Clear
void data();            // Ambiguous. What about the data?

Use Consistent Case Styles

Different programming languages and cultures prefer different casing styles. Within a project, pick a style and stick with it.

Example using camelCase:

string userDetail;     // camelCase
string UserDetail;     // PascalCase (not consistent with camelCase)

Here’s a list of common case styles used in programming with examples for each:

  1. Camel Case (camelCase): The first letter of the first word is lowercase, and the first letter of each subsequent concatenated word starts with an uppercase letter.
    • Example: userProfile, calculateTotalAmount
  2. Pascal Case (PascalCase): Similar to camel case, but the first letter of the first word is also uppercase.
    • Example: UserProfile, CalculateTotalAmount
  3. Snake Case (snake_case): Words are separated by underscores and all letters are lowercase.
    • Example: user_profile, calculate_total_amount
  4. Kebab Case (kebab-case): Words are separated by hyphens.
    • Example: user-profile, calculate-total-amount
  5. Space Case: Words are separated by spaces (not commonly used in programming due to languages not supporting spaces in variable names, but used in regular writings).
    • Example: User Profile, Calculate Total Amount
  6. Screaming Snake Case (SCREAMING_SNAKE_CASE): Like snake case, but all letters are uppercase.
    • Example: USER_PROFILE, CALCULATE_TOTAL_AMOUNT
  7. Studly Caps: Alternating capitalization, usually starting with an uppercase letter. This isn’t standard and can vary.
    • Example: UsErPrOfIlE, CaLcUlAtEtOtAlAmOuNt
  8. Train Case (Train-Case): Similar to Pascal Case, but words are separated by hyphens. It’s like a combination of Pascal Case and Kebab Case.
    • Example: User-Profile, Calculate-Total-Amount
  9. Start Case (Start Case): All words are capitalized and separated by spaces. Commonly used in titles or headers.
    • Example: User Profile, Calculate Total Amount
  10. Lower case (lowercase): All characters are lowercase without any separation for multiple words.
    • Example: userprofile, calculatetotalamount
  11. Upper case (UPPERCASE): All characters are uppercase without any separation for multiple words.
    • Example: USERPROFILE, CALCULATETOTALAMOUNT

These are the major case styles commonly seen in programming and documentation. Depending on the context (e.g., specific languages, platforms, or company conventions), one may encounter variations of these or even combinations. It’s important to adhere to the established conventions for the specific context in which you’re working.

Avoid Magic Numbers

Magic numbers, or unexplained numerical values directly embedded in the code, can be a significant source of confusion for developers. Their presence often harms readability because their purpose or origin is unclear. Instead of using these arbitrary values, assigning them to descriptively named constants is much more advisable. By doing so, the code becomes more understandable and easier to maintain. If the value ever needs to change, updating the named constant ensures consistent and error-free adjustments throughout the codebase.

Example:

double calculateInterest(double principal) {
    return principal * 0.05;  // What's 0.05?
}

// Better:
const double INTEREST_RATE = 0.05;
double calculateInterest(double principal) {
    return principal * INTEREST_RATE;
}

Consider Domain-Specific Naming

Names should align with the terminology of the application’s domain. This creates a bridge between the technical and the business or scientific world. Developers can create more intuitive and relatable code by ensuring names align with the terminology used in the business, scientific, or other specialized areas. This practice creates a bridge between the technical aspects of the code and the real-world concepts it represents. It aids both developers familiar with the domain and domain experts without a technical background in understanding the software’s functionality. As a result, domain-specific naming fosters better collaboration and clarity across interdisciplinary teams.

Example in a medical app:

int patientCount;  // Aligned with medical terminology
int userCount;     // Too generic

Avoid Overloading Well-known Names

Familiar names come with expectations. Don’t use them in non-standard ways. Certain names carry widely recognized meanings and connotations. Utilizing these familiar names in unconventional ways can confuse and mislead developers. When a name comes laden with specific expectations, using it out of its recognized context can hinder clarity. For instance, choosing “data” as a variable name is too broad and ambiguous. It risks clashing with virtually any representation of data in the code, potentially leading to misunderstandings and errors. Therefore, it’s essential to select names that precisely convey their purpose, avoiding general terms that can dilute the clarity and specificity of the code.

Here’s a list of widely-recognized variable names (or terms) with specific meanings in many coding contexts:

  1. i, j, k: Commonly used as loop counters, especially in nested loops.
  2. n: Often denotes the number of elements in a collection or the size of an array.
  3. str: Typically indicates a string variable.
  4. max and min: Used to represent maximum and minimum values, respectively.
  5. tmp or temp: Temporarily stores data, often during data swapping or processing.
  6. len or length: Denotes the length of a data structure, like a string or array.
  7. pos or position: Indicates a position or index in a data structure.
  8. count: Tracks the number of occurrences or elements.
  9. sum: Accumulates values, usually in the context of arithmetic operations.
  10. avg or average: Represents the average or mean of a collection of numbers.

Using these names outside of their traditional contexts can introduce confusion. For instance, naming a string variable count or using avg as a loop counter can be misleading and should be avoided to maintain code clarity.

Evolution with Refactoring

Code evolves, and so should its names. As functionality changes, update the names to reflect those changes.

Example:
Originally:

void calculateTax();

After adding more functionalities:

void calculateTaxAndDeductions();

Abbreviations and Acronyms

While some acronyms are universally recognized, excessive abbreviations can obscure meaning. When in doubt, spell it out.

Example:

int empCount;      // Might be clear in a HR context
int employeeCount; // Clear in any context

Comments vs. Names

If you need a comment to explain a name, the name is probably not clear enough. Aim for self-explanatory names.

Example:

// This requires a comment to understand:

// Seconds since the epoch
int s{0};  

// This is better:
int secondsSinceEpoch{0};

Avoid Redundancy

Names should be concise without being repetitive or stating the obvious. The clarity and precision of naming can significantly affect code readability. Names should be elegantly concise, eliminating any form of redundancy or the need to state the evident. It’s a delicate balance to strike: names should encapsulate the essence of the variable or function without echoing information that’s already implied. For instance, naming a variable stringName is a redundancy since the term “string” already suggests it’s a name. By avoiding such redundant, repetitive nomenclature, developers can ensure that their code remains both succinct and straightforward, avoiding the pitfalls of redundancy.

Example:

string nameString;  // Redundant. We know it's a string.
string name;        // Clear and concise

The DRY Principle

The DRY (Do Not Repeat Yourself) principle, a foundational tenet in software development, underscores the importance of avoiding redundancy in code logic and naming conventions. When applied to naming, this principle advocates for consistent and singular terminology for a given concept or entity throughout a codebase. Divergence from this, such as employing multiple names for the same concept, can lead to confusion, misinterpretation, and higher cognitive overhead for developers. For instance, interchanging terms like customerID, clientID, and userIdentifier within different parts of an application can be misleading. By adhering to the DRY principle in naming, developers can create clearer, more maintainable code, ensuring that each unique concept or entity is represented uniformly, facilitating better comprehension and collaboration.

Balancing Name Length

While clarity is paramount, extremely long names can clutter the code. Find a balance between descriptiveness and brevity.

Example:

int numberOfDaysBetweenTheStartAndEndDate; // Overly long
int daysBetweenDates;                      // Balanced

Incorporating these principles ensures a more intuitive, readable, and maintainable codebase. They underpin a culture of clarity, consistency, and effective communication in software development.

Strategies and Techniques for Effective Naming

Creating a Schema

In programming, a schema typically refers to a defined structure or blueprint. When applied to naming, it means establishing a systematic plan or pattern for how entities should be named. Creating a schema serves as a framework for naming, ensuring consistency, and reducing decision fatigue. This approach is particularly valuable in larger projects with multiple collaborators to ensure everyone adheres to a shared naming convention.

1. Purpose-driven Schema:
Identify the primary function or purpose of the variable or function and incorporate it into the name.

Example:

// Without schema:
double db;
double dc;

// With a purpose-driven schema:
double debitBalance;
double debitChange;

2. Hierarchy in Naming:
For entities with a clear hierarchy or order, reflect this hierarchy in the naming schema, which can make finding related classes or objects more intuitive in modern IDEs.

Example in a graphics context:

class Shape {...};            // Base class
class Triangle : public Shape {...};  // Generic triangle
class TriangleIsosceles : public Triangle {...};  // Specific type of triangle
class TriangleEquilateral : public Triangle {...};  // Another specific type

3. Scope-based Schema:
Prefixes or suffixes can indicate the scope or lifecycle of a variable.

Example:

// Using a camelCase schema with a scope-based prefix:
string localUserName;     // A variable with local scope
string globalUserDatabase; // A global variable or resource

4. Modular Naming:
In modular systems, the schema could reflect the module’s name or function.

Example in a networking module:

void networkingInitialize();
void networkingTerminate();

5. Action-result Schema for Functions:
Especially for functions returning values, the name should indicate both the action taken and the expected result.

Example:

bool isUserValid();       // Action: checking, Result: boolean validity
string fetchUserDetail(); // Action: fetching, Result: user detail

Benefits of Using a Schema:

  • Consistency: A predefined schema brings consistency to the naming throughout the codebase, enhancing code readability and understandability.
  • Predictability: With familiarity, developers can often predict function or variable names, speeding up the development process and easing the onboarding of new team members.
  • Reduced Cognitive Load: A schema negates the need for developers to deliberate on names every time. Instead, they can follow the schema, conserving mental energy and time.

To conclude, crafting a naming schema isn’t about enforcing rigid rules but offering a guide to streamlining naming processes. It ensures clarity, predictability, and aids in the development of a more maintainable and understandable codebase.

Implementing the A/HC/LC Pattern

The A/HC/LC naming pattern offers a systematic approach to name entities in software development, ensuring clarity, coherence, and searchability. While frequently applied to function naming, its principles can be extended across classes, variables, and more. Below, we delve deeper into this tripartite naming system and its application.

action (A) + high context (HC) + low context? (LC)

Purpose of the A/HC/LC Pattern for Naming:

  • Readability & Context: Provides a clear understanding of an entity’s purpose, its primary subject, and any nuanced context.
  • Intuitive Grouping: In IDEs or codebases, related entities group together, making the navigation smoother.
  • Consistency: A standard naming convention means reduced cognitive overhead for developers, leading to quicker code comprehension.

Components of the A/HC/LC Pattern

  1. Action (A): Typically a verb, it indicates what the primary operation or behavior associated with the entity is.
  2. High Context (HC): This is the primary subject or the main entity in focus. It offers a broad context to understand the scope.
  3. Low Context (LC): Offering a secondary layer of specificity, it narrows down the scope or provides additional information about the HC.

Take a look at how this pattern may be applied in the table below.

NameAction (A)High context (HC)Low context (LC)
getUsergetUser
getUserMessagesgetUserMessages
handleClickOutsidehandleClickOutside

Examples in Various Domains

  1. Functions:
  • calculateTriangleArea: Here, “calculate” is the action, “Triangle” is the high context, and “Area” is the low context.
  • getUserProfile: “get” is the action, “User” is the high context, and “Profile” is the low context.
  1. Classes:
  • TriangleIsosceles: “Triangle” as the high context gives a broad categorization, and “Isosceles” as the low context provides the specificity.
  • ButtonSubmit: Here, “Button” is the high context, and “Submit” is the low context, specifying the type of button.
  1. Variables:
  • counterPageViews: “counter” suggests the action or purpose, “Page” is the high context, and “Views” offers a narrower context.
  • statusUserActive: “status” denotes purpose, “User” is the high context, and “Active” is the low context indicating the type of status.

When adopting the A/HC/LC pattern, ensuring that the chosen names align with the domain language and resonate with the team’s understanding is essential. As projects evolve, periodic refactoring to maintain the integrity of this naming convention can further enhance codebase clarity.

Prioritizing Machine Readability

Names often serve dual purposes: they must be interpretable by both developers and machines. When a name is designed with machine readability as a priority, it ensures ease of automation and enforces structure and consistency that benefits human understanding. Below, we explore why machine-readable names are valuable, coupled with strategies and examples for achieving them.

Why Machine Readability Matters

  1. Automation & Tooling: Machine-readable naming conventions empower developers to create automated tools, scripts, or utilities that interact with the codebase. This could involve auto-generating documentation, reports, or implementing custom refactorings.
  2. Consistency & Searchability: A name that adheres to a predictable structure can be reliably searched, replaced, or pattern-matched across large codebases.
  3. Error Reduction: Predictable naming reduces the risk of overlooking instances during manual refactoring or modifications.

Strategies for Machine-Readable Naming

  1. Adopt a Structured Schema: Employ the A/HC/LC pattern or other established naming conventions that ensure a consistent structure. Example: Using the format actionSubjectModifier for function names like calculateTriangleArea ensures a clear pattern for both developers and machines.
  2. Regular Expression Friendliness: Design names such that regular expressions can easily capture them. This helps in pattern matching and facilitates operations like searching or refactoring. Example: If all database-related functions begin with db, a regex like ^db[A-Z].* can quickly identify them.
  3. Avoid Ambiguities: Eschew characters or patterns that might confuse parsers or regular expressions. Example: Avoid names with irregular characters or spaces that might need excessive escaping in regex patterns. Instead of get-data/value, prefer getDataValue.
  4. Consistent Case Styles: Employ consistent casing, such as camelCase or PascalCase, which can be systematically parsed. Example: In a camelCase convention, a parser can detect word boundaries by identifying uppercase characters, turning parseInputString into [“parse”, “Input”, “String”].
  5. Plan for Global Operations: Make names unique and structured enough that a global search-and-replace would be reliable and predictable. Example: If all event-handler functions are prefixed with handle, then renaming an event type becomes a trivial global replacement operation.

In Practice: Imagine developing a parser for a function name. If the rules governing the name’s structure are machine-readable, this parser can confidently dissect the name into its meaningful components. Such a parser might break down retrieveUserDetails into the action “retrieve,” the high context “User,” and the low context “Details.”

While the intent behind machine-readable naming might seem automation-centric, it invariably leads to better clarity, organization, and maintainability for developers. It’s an investment in the codebase’s future, streamlining operations and ensuring longevity.

Emphasizing Scalability in Naming

Software development is a dynamic process. As projects evolve and scale, the names we coin today must accommodate the growth of tomorrow. By adopting scalable naming conventions, developers can ensure that the system remains organized and maintainable even as its dimensions expand. Let’s delve into the importance of scalable naming and explore strategies to achieve it.

The idea is simple: If a naming convention crumbles under the weight of growth, it is not scalable. A classic pitfall is the filename.ext.old approach to versioning. This approach immediately fails as soon as there’s a second older version. When devising a naming convention, it’s essential to consider its longevity and how it would stand up against increased volume.

Strategies for Scalable Naming

  1. Future-Proofing with Magnitude: Before settling on a convention, simulate its behavior at significantly larger scales. For instance, if you’re naming log files for a system that generates ten logs daily, how would your naming approach fare if it were generating a thousand or ten thousand logs?
  2. Structured Metadata in Names: Integrate meaningful metadata into names, which can facilitate sorting, searching, and identifying. This ensures that even with vast numbers, each name remains unique and informative. Example: Consider naming log files. Instead of just having program_log.txt, a scalable approach would be program_YYYYMMDD_##.log, where YYYYMMDD represents the date and ## is a serial number for that day. This approach remains clear and structured, even when there are thousands of logs.
  3. Avoid Ambiguities: Ensure that the naming convention doesn’t introduce potential ambiguities as it scales. An ambiguity-free name remains unique regardless of the number of entries.
  4. Automate Name Generation: Whenever possible, automate the naming process to ensure that conventions are followed consistently, especially as the volume of names increases. Example: Utilizing a script to generate log file names like "program_" + date() + "_" + pad(serialNumber, 2) + ".log" ensures consistency and avoids human errors or oversights.
  5. Reserve Space for Expansion: If numbering is part of the convention, ensure there’s sufficient space for growth. For instance, if you suspect a system might produce up to a hundred logs in a day, use at least three digits for the serial number.

In Practice: Consider a server application that generates logs. Initially, it produced one log daily named serverLog_1.txt. Soon, there were many logs, and the naming became chaotic with names like serverLog_old.txt, serverLog_new.txt, and serverLog_final.txt. By switching to a scalable naming convention like server_YYYYMMDD_###.log, the team ensured a clear, organized, and scalable system for log management.

Emphasizing scalability in naming conventions is an investment in the future clarity and maintainability of the system. It anticipates growth and ensures that the naming remains coherent and meaningful, regardless of the volume or complexity that time might introduce.

Making Names Pronounceable

While much of our work is silent typing, our cognitive processing of code often isn’t. Many developers internally vocalize variable names and functions when they read them, either audibly or subvocally. This subtle vocalization aids comprehension and memory recall. As such, making names pronounceable can significantly improve code readability and maintainability. Let’s explore the importance of pronounceable names and some strategies to ensure they are easy to vocalize.

Why Pronounceable Names Matter

  1. Ease of Communication: Pronounceable names facilitate smoother conversations when discussing code with colleagues.2
  2. Enhanced Comprehension: Our brains are wired to process language. Names that align with our linguistic capabilities are easier to understand and remember.
  3. Typing Fluency: Pronounceable names can be typed more effortlessly. Our fingers get accustomed to typing words or phrases that have linguistic structures, leading to fewer errors and faster typing.

Examples of Non-Pronounceable vs. Pronounceable Names

  1. Non-Pronounceable:
   auto dla2ny = ComputeDistance(losAngeles.getCoords(), newYork.getCoords());

This might be read as “dee-laa-too-nee” in one’s mind, which isn’t intuitive.

  1. Pronounceable:
   auto distanceLa2Nyc = ComputeDistance(losAngeles.getCoords(), newYork.getCoords());

This translates smoothly to “distance L.A. to NYC,” making it much clearer.

Strategies for Creating Pronounceable Names

  1. Use Full Words: Whenever possible, use complete words rather than abbreviations. ComputeDistance is more straightforward than CompDist.
  2. Limit Number Incorporation: While numbers can be a concise way to convey information, they can muddle pronunciation. If numbers must be used, place them where they least disrupt the word’s flow.
  3. Avoid Jargon: Unless the jargon is universally understood in the specific domain, it can create pronunciation challenges.
  4. Test Names Aloud: After naming a variable or function, say it aloud. If it feels awkward or forced, consider renaming.
  5. Solicit Peer Feedback: Sometimes, what’s pronounceable to one person might not be to another. It’s beneficial to get a second opinion.

The names we choose in our code have a substantial impact on how we, and others, perceive and interact with our work. By prioritizing pronounceability, we make our code more accessible, both mentally and verbally. As developers, our aim is to write code that not only machines can interpret but also our peers and our future selves can readily understand. Emphasizing pronounceable names is a step in the right direction.

Utilizing Delimiters

Delimiters play an indispensable role in shaping the readability, structure, and machine-friendliness of names across various programming contexts. From filenames to function names, delimiters segment and clarify components within names, offering semantic hints and ensuring conformity with naming standards. Let’s delve into their usage, focusing on common delimiters in naming.

At their essence, delimiters are characters that separate strings or data elements in a given sequence. Their primary purpose is to enhance readability, ensure structural integrity, and maintain naming conventions across different contexts.

Common Delimiters in Naming

  1. CamelCase: Often used for variable and function names, this convention capitalizes the first letter of each word except the first, without spaces or punctuation. It makes names easily readable and conforms to most programming language’s standards.
   let userFirstName = "John";
   function calculateTotalAmount() {...}
  1. PascalCase: Similar to CamelCase but with the first letter capitalized, it’s widely adopted for class names in object-oriented programming.
   public class ShoppingCart {...}
  1. snake_case: Uses underscores to separate words. Predominantly found in languages like Python or in file naming conventions.
   user_last_name = "Doe"
   configuration_file.txt
  1. kebab-case: Employs hyphens to divide words. Often found in URLs or CSS class names due to its URL-friendliness.
   .header-container {...}
  1. Spaces: In file or folder naming, especially in non-programming contexts, spaces often act as delimiters. However, they can introduce complexities in command-line operations or URLs.
   Project Plan 2023.docx
  1. Dot (.) Delimiter: Widely used in file extensions to indicate file type, separating the filename and the extension.
   report.pdf
   image.jpeg

Points to Consider When Using Delimiters

  1. Language & Platform Conventions: Always be aware of the prevailing conventions in your programming language or platform. Using the wrong delimiter can introduce errors or make your code harder to read for others familiar with the convention.
  2. Machine-Readability: Some delimiters, like spaces in filenames, can complicate automated tasks, especially in command-line environments. When in doubt, choose delimiters that simplify machine operations.
  3. Human Readability: While CamelCase might be perfect for variable names, it might not be the best for longer, descriptive names where snake_case or kebab-case could enhance readability.
  4. Consistency is Key: Once you’ve chosen a naming convention for a project, stick with it. Consistency aids in comprehension and reduces cognitive load.

Delimiters, in all their subtle variations, serve a foundational role in shaping names across coding and file naming conventions. By understanding their nuances and employing them judiciously, developers can ensure clarity, maintainability, and a seamless integration of their work within the larger coding ecosystem.

Avoiding Context Duplication

Context duplication in code can lead to verbosity, redundancy, and confusion. By being mindful of the context in which a name will be used, developers can craft concise and intuitive naming conventions that improve the clarity and readability of the codebase. Below, we will explore the pitfalls of context duplication and how to mitigate them effectively.

Understanding Contextual Redundancy

Consider a class called MenuItem. One of its potential responsibilities could be to handle a click event. A direct approach to naming this functionality might yield:

virtual void handleMenuItemClick(const Event& x);

However, when we put this function into action, an evident redundancy emerges:

menuItem->handleMenuItemClick(event);

The term “MenuItem” is reiterated in both the object and the function call, which leads to unnecessary verbosity and repetition. Over time, as the codebase grows, these small repetitions can accumulate, making the code harder to read and maintain.

Refactoring to Reduce Duplication

Aware of this redundancy, a more concise approach to naming the function would be:

virtual void handleClick(const Event& x);

Applied in the code, this reads:

menuItem->handleClick(event);

This naming convention retains clarity without redundancy. It’s clear, given the context, that we’re handling a click event specifically for the MenuItem. As more items or object types are introduced, each can adopt the handleClick function, promoting a consistent and intuitive naming convention across the API.

Additional Benefits and Considerations

  1. Scalability: Avoiding context duplication allows for easier scalability. As the application grows and new classes and functionalities are added, the API remains consistent.
  2. Enhanced Collaboration: For team projects, reducing redundancy can lead to fewer misunderstandings and misinterpretations among developers.
  3. Optimizing Refactoring: In the future, if the functionality of the function changes slightly, the name might not need to be changed if it’s already context-free, minimizing the places to update.
  4. Recognizing Patterns: While it’s crucial to avoid redundancy, it’s equally important to recognize patterns and conventions in the codebase to maintain consistency.

Eliminating context duplication is about more than just concise naming; it’s about crafting an intuitive, scalable, and maintainable codebase. By always being aware of the broader context and aiming to reduce verbosity, developers can ensure that their code remains agile and clear.

Ensuring Better Naming on Disk

Effective naming and organization of files and directories on disk significantly enhance a developer’s experience. It provides clarity, facilitates navigation, and augments maintainability. For C++ and Python developers, aligning the filesystem’s structure with the code’s organization is paramount. Here’s how to achieve this in your projects:

Hierarchical Structures for Scalability

Adopt a directory structure that reflects the organization of classes, namespaces (C++), or modules (Python) in the code. This parallelism ensures that developers can intuitively navigate both the filesystem and the codebase.

Example:

ProjectRoot/
│
├── src/
│   ├── models/
│   ├── controllers/
│   └── views/
│
├── include/
│   ├── models/
│   ├── controllers/
│   └── views/
│
└── test/

Direct Reflection in File Names

Ensure the names of files directly mirror the primary classes, functions, or namespaces/modules they contain. This 1:1 correlation guarantees immediate clarity.

Example:

  • In C++: For a class named UserProfile, the file should be named UserProfile.cpp (for source) and UserProfile.hpp (for header).
  • In Python: For a module named user_profile, the file should be named user_profile.py.

Directory Structures Echoing Namespaces/Modules

In C++, let directories parallel the namespaces of your code. For Python, directories can mimic module structures. This provides a seamless experience between navigating directories and code.

Example:

  • For a C++ namespace structure like myapp::models, the directory structure might be /myapp/models/.
  • In Python, if you have a module structure like myapp.models, then the directory structure could follow as /myapp/models/.

Avoid Special Characters and Spaces

Ensure portability by avoiding special characters in filenames. Use consistent capitalization to separate words, matching your code’s naming convention.

Example:

  • Bad: report 10/2023.cpp
  • Good: Report202310.cpp

Versioning and Timestamping

When files require versions or frequent updates, incorporate version numbers or timestamps. Avoid ambiguous terms.

Example:

  • Bad: BackupOld.db
  • Good: Backup20231010.db

Consistency Across All Files

Be consistent in your naming. If you’re using PascalCase for class names in C++, apply the same for filenames. For Python, if you’re using snake_case for module names, reflect that in your filenames.

Example for C++:

  • Class in code: PaymentModule
  • Filename: PaymentModule.cpp and PaymentModule.hpp

Example for Python:

  • Module in code: payment_module
  • Filename: payment_module.py

For C++ and Python developers, naming files and directories in a manner that echoes the code’s organization is invaluable. This eases navigation and promotes a coherent and streamlined development workflow.

Establishing 1:1 Relationships

Establishing a clear 1:1 relationship between various entities is a potent strategy to maintain consistency, reduce ambiguities, and accelerate productivity. These relationships ensure a single source of truth and symmetry, and enhance the developer’s ability to anticipate the structure in different domains based on familiarity with one.

Jira Issues and Git Branches

One of the most prominent practices in contemporary development is aligning task-tracking software with source code repositories. Using the Jira issue name as the Git branch name is a clear way to create this relationship.

Example:

  • Jira Issue: PROJ-123
  • Git Branch: feature/PROJ-123

This ensures that any developer looking at the branch immediately knows the context and can trace back to the detailed issue in Jira if needed.

Class Names and File Names (C++ & Python)

In C++ and Python, creating a direct correlation between class names and the files they reside in fosters predictability. If you come across a class in the codebase, you can swiftly locate its file and vice versa.

Example:

  • C++ Class: UserProfile
    • File Name: UserProfile.cpp and UserProfile.hpp
  • Python Class: DatabaseConnection
    • File Name: DatabaseConnection.py

Configuration Variables and Environment Variables

When deploying software, it’s common to use environment variables for configurations. Naming these environment variables after the configuration they represent provides clear insight into their purpose.

Example:

  • Configuration: DATABASE_CONNECTION_STRING
  • Environment Variable: DATABASE_CONNECTION_STRING

Further, this naming convention of using ALL_CAPS for global variables means that when I see this in the code, I automatically know that it is global without having to reference any documentation!

Database Tables and Model Classes

When working with ORM (Object-Relational Mapping) tools, ensure that the model class names directly correlate with the database tables they represent. This clear mapping streamlines database-related operations.

Example:

  • Database Table: users
  • C++/Python ORM Model Class: User

API Endpoint Paths and Controller Method Names

When designing APIs, ensure that the names of the endpoint paths provide a clear hint to the corresponding controller methods.

Example:

  • API Endpoint: /getUserProfile
  • Controller Method: getUserProfile()

Asset Folders and Asset Types

For projects involving multimedia or other assets, ensure that the folder names directly represent the type of assets contained within them.

Example:

  • Asset Type: Icons
  • Folder Name: Icons

By establishing 1:1 relationships, developers can significantly reduce the cognitive load of navigating complex systems. It fosters a sense of symmetry, making the transition between different domains almost instinctive. This congruence ensures that knowledge in one area directly translates to understanding in another.

Using Tool-provided Contexts

The tools we use often provide contexts that can aid in managing and navigating our projects. By understanding and respecting the default behaviors of these tools, developers can streamline workflows and make information retrieval more intuitive.

File Sorting in File Managers and Consoles

Both the Windows File Manager and terminal directory listings, like ls in Unix-based systems, sort files alphanumerically by default. This sorting behavior can be harnessed to our advantage by ensuring our naming conventions align with this default behavior.

Example:

Suppose we have log files generated daily. Instead of naming them with just the day of the month, pad the name with leading zeros for single-digit days:

  • Incorrect: Log_1_August.log
  • Correct: Log_01_August.log

For files beginning with dates, format them as YYYYMMDD to get them sorted chronologically:

  • Incorrect: 1_August_2023.log
  • Correct: 20230801.log

Using Intuitive Hierarchies in IDEs

Modern Integrated Development Environments (IDEs) offer various contexts that help developers navigate large codebases efficiently. For instance, classes, methods, and properties are hierarchically presented. Adhering to a consistent naming convention within this structure can make navigation more intuitive.

Example in C++:

Class: Transaction

  • Method: paymentProcess()
  • Method: paymentRefund()

Utilizing Filters in Version Control Systems

Tools like Git allow users to filter branch names, tags, or commits. If branches are named following a clear pattern, finding the required branch becomes a trivial task.

Example:

Using Jira Issue numbers as their names, feature branches can be prefixed with feature/, and bug fix branches with bugfix/:

  • feature/project-123
  • bugfix/project-123

Using Semantic Versioning for Libraries and APIs

Tools like package managers (e.g., pip for Python or npm for JavaScript) sort versions numerically. Using semantic versioning not only makes versioning meaningful but also ensures that tools list versions in a logical order.

Example:

  • 1.0.1 (Patch release)
  • 1.1.0 (Minor release)
  • 2.0.0 (Major release)

By embracing the contexts provided by tools and respecting their default behaviors, developers can eliminate many potential pitfalls and optimize workflows. These practices don’t just simplify the current task, but they lay a foundation for more predictable interactions in the future.

Standardize Date Formats for Consistency

When it comes to date representations in code, file naming, or even in comments, uniformity ensures readability, scalability, and easy querying. A standardized date format reduces the cognitive overhead of second-guessing or interpreting various potential arrangements.

Let’s break down the YYYYMMDD format and understand its advantages:

Unambiguous Representation

Given the format YYYYMMDD, there’s no room for confusion between day and month. In contrast, a format like MM/DD/YY or DD/MM/YY is ambiguous, especially when sharing data across international teams.

Example:
20230704: We immediately know this is July 4th, 2023.

Alphanumeric Sorting

When dates are presented in the YYYYMMDD format, alphanumeric sorting naturally results in chronological ordering. This is particularly beneficial in scenarios like filename conventions, ensuring that the latest file is either at the top or bottom of a list.

Example for File Sorting:

  • Report_20230701.txt
  • Report_20230702.txt
  • Report_20230703.txt

Flexibility with Date Resolutions

Even if the day’s resolution isn’t necessary, adhering to the principle of year preceding month and month preceding day provides clarity. For instance, YYYYMM format still maintains clarity and order.

Example:

  • Year and Month: 202307

ISO 8601 Adaptability

The YYYY-MM-DD format, a variant with separators, is an international standard under ISO 8601. (A fantastic reasonable default!) While it introduces separators, it still maintains the clear hierarchy of year, month, and day. Tools and systems that respect ISO 8601 can easily parse this format, making it a good choice for interoperability.

Example:

  • 2023-07-04

Always Using Two Digits for Months and Days

Using two digits for months and days, even when they are below 10, ensures they sort correctly and reduces ambiguity.

Example:

  • Incorrect: 202374.txt
  • Correct: 20230704.txt

Developers can create a consistent, unambiguous, and machine-friendly environment by adhering to the YYYYMMDD format or its ISO 8601 sibling. This consistency greatly aids in data management, querying, and general readability across projects.

Avoid Contractions

Clarity is paramount. One of the pitfalls developers often fall into in their pursuit of brevity is the use of contractions. Contractions are shortened forms of words or phrases. While they might save a few keystrokes, they significantly hamper the readability and understanding of the code.

Why avoid contractions? Several reasons.

  1. Ambiguity: Contractions can lead to ambiguities. A reader might not always decipher the intended meaning of a contraction correctly. For instance, btn might stand for button, but it could also mean between, bitten, or any number of things in a different context.
  2. Inconsistency: Contractions can introduce inconsistencies in naming. If one developer uses itm for item and another uses it, it leads to a disjointed codebase.
  3. Reduced Searchability: Searching for standard terms in the codebase becomes harder. If you search for button, you might not find occurrences where btn has been used.
  4. Decreased Onboarding Efficiency: For new team members, unfamiliar contractions add another layer of complexity to the onboarding process. Instead of focusing on logic and architecture, they grapple with the semantics of unfamiliar contractions.

Poor Practice: Using contractions

int calcDistBtwnPts(Point a, Point b);
void updBtnState();
std::string getFnameFromDb();

Best Practice: Avoiding contractions

int calculateDistanceBetweenPoints(Point a, Point b);
void updateButtonState();
std::string getFilenameFromDatabase();

In the above examples, the clarity achieved by avoiding contractions makes the code more understandable. It also improves the self-documenting nature of the code.

Choosing descriptiveness over brevity leads to more maintainable, readable, and collaborative code. Contractions, though tempting for their succinctness, detract from these goals. Always prioritize the future readability of your code over the momentary convenience of using a contraction. Remember, code is read more often than it’s written.

Manage Singular vs. Plural Names

Choosing between singular and plural forms when naming variables, functions, classes, or resources is a common dilemma in software development. The goal is to achieve clarity and express intent as accurately as possible. Here, we’ll explore the conventions around singular and plural naming and offer guidelines to make your code more intuitive.

Singular Naming

Use singular names when referring to a single instance of an item.

Good Practice:

class User {
    //...
};

User user;

Poor Practice:

class Users {
    //...
};

Users user;  // Misleading, as this refers to a single user.

Plural Naming

Use plural names when referring to collections or multiple instances of items.

Good Practice:

std::vector<User> users;

void listUsers() {
    //...
}

Poor Practice:

std::vector<User> userList;  // Using 'List' suffix is redundant with 'vector'.

Edge Cases & Considerations

  1. Methods that Return Collections: A method that retrieves multiple items should have a plural name. Good Practice:
   std::vector<Order> getPendingOrders();
  1. API Endpoints: In RESTful services, it’s common to use plural nouns for resource names. Good Practice:
   GET /users        // Retrieve list of users
   GET /users/1      // Retrieve details of user with ID 1
  1. Boolean Flags: For boolean flags that indicate the presence or absence of multiple items, use plural names. Good Practice:
   bool hasErrors{false};
  1. Be Consistent: Whatever you decide, be consistent across your codebase. If you choose to use plural names for collections, do so everywhere.
  2. Language Nuances: Some programming languages have specific singular and plural form conventions. For instance, in Rails, models are singular (e.g., User) while database tables and controllers are plural (e.g., users). It’s important to be aware of such conventions.

The choice between singular and plural forms comes down to clarity and the representation of intent. By following consistent conventions and accurately representing real-world entities in your code, you can make your codebase more intuitive and maintainable.

Advanced Naming Principles

Crafting a Better API

Crafting a robust and user-friendly API requires a deep understanding of the audience, the domain, and a good grasp of naming conventions. The following principles can guide developers in creating an API that is both intuitive and consistent.

Symmetry for User Understanding:

Ensure symmetry in API function pairs, where for every action, there’s a corresponding counter-action, or for every getter, there’s a setter. This brings predictability to the API.

Example:

  • openConnection() and closeConnection()
  • setThreshold(int value) and getThreshold()

The table below shows some examples of symmetry in action.

API ActionGood SymmetryBad Symmetry
setgetfetch
beginendstop
startstopfinish
readFilewriteFileoutputFile

Avoid Synonyms and Repeated Information

Steer clear of using different words to mean the same thing, as this can confuse users. Also, avoid repeating information that the user can easily infer.

Example:
Instead of having addUser(), insertUser(), and createUser(), pick one clear action word and stick with it throughout the API.

Prefer Intuitive Prefixes in APIs

Using intuitive prefixes can make the purpose of a function immediately apparent. This especially holds in libraries where different categories of functions exist.

Example:
In a graphics library:

  • drawCircle(), drawSquare()
  • fillEllipse(), fillRectangle()

Avoid Member Variable Prefixes

Using prefixes like ‘m_’ or ‘_’ for member variables is a debated practice. While some believe it aids in distinguishing member variables, it can also clutter the API, especially when getters and setters are auto-generated. Modern IDEs highlight member variables differently, making such prefixes less useful.

Example:
Instead of m_value and getValue(), prefer value and value() or getValue().

Normalize API Calls

Ensuring API calls follow a consistent pattern or template aids users in guessing function names and reduces the learning curve.

Example:
For a database API:

  • fetchRecord(), updateRecord(), deleteRecord()

By following these principles, an API can become a joy to use, enabling users to guess function names easily, rely on consistent patterns, and quickly become productive.

Separate what the machine and developer needs, from what the user needs

I’m looking at you, Imperial Units.

Working on an engineering project, standardize around using only SI units. If you do so, the amount of documentation you need is drastically reduced, the API is more obvious, and math conversions are more consistent. (You get all that for free.)

// This could be an obvious API or a non-obvious API
// It all depends on my convention.
void setDistance(double x);
double getFrequency() const;

If I standardized on SI units for my API, then I know automatically that distances are in meters and the frequency is in Hertz.

I have heard the argument many times: “Pilots want Miles, not Kilometers!” Right, but pilots don’t use my API, and I’m not writing my API to use miles!

Convert to some user-oriented unit on output. Or convert user-oriented input when we read it in (and validate it). Do not confuse what the user needs with what the API needs.

Do not confuse what the user needs with what the API needs.

Good API design says that an API should have ONE way to do a thing. I think I learned this from Martin Reddy’s API Design for C++. Given this, avoid the temptation to add conversion setters and getters in your API. (I.e., setDistanceMeters and setDistanceFeet and setDistanceMiles.)

Promoting Copy/Paste with Standardized Naming

Crafting a Better API

Standardization in naming is more than a matter of aesthetics or personal choice; it’s about efficiency, clarity, and maintainability. In the real world, developers often reuse or repurpose existing code. Copy-paste-edit is a common pattern. To support this, having consistency in naming can make the developer’s job simpler and faster.

Make things that are the same called the same thing:

The principle is simple: If two things are doing fundamentally the same action, they should be named consistently. This reduces the cognitive overhead of remembering or deducing what a particular piece of code does.

For code clarity, you should use the same name when referencing the same concept or action in different parts of your codebase. This reduces ambiguity and makes the codebase more navigable.

Consider the example of reading a file into a string:

Initial Code:

void ConfgFile::readConfig(const std::filesystem::path& fname )
{
    std::ifstream configFile(fname);
    configFile.seekg(0, std::ios::end);
    size_t size = configFile.tellg();
    configFile.seekg(0);
    configFile.read(&this->buff[0], size);
}

Refactored Code for more modern C++:

void DataFile::readFile(std::string_view fileName)
{
    std::ifstream dataFile(fileName);
    this->buffer << dataFile.rdbuf();
}

These code snippets achieve the same goal but with different variable names, making them incompatible when you want to reuse or merge them.

Optimized and Standardized Code:

void ConfigFile::read(const std::filesystem::path& x)
{
    std::ifstream ifs(x);
    this->buffer << ifs.rdbuf();
}

void DataFile::read(const std::filesystem::path& x)
{
    std::ifstream ifs(x);
    this->buffer << ifs.rdbuf();
}

In the optimized code, consistency is king:

  • The input parameter is always called x.
  • The file stream, whether input or output, has a standardized abbreviation (ifs for std::ifstream and ofs for std::ofstream).
  • The buffer is always named buffer.
  • Functions that read files are simply named read.

Promoting this level of consistency makes the code easier to read and understand, and the ease of copy-pasting and reusing code segments is also greatly enhanced. Furthermore, a developer familiar with one part of the codebase would find other parts immediately recognizable and navigable. This enhances collaboration, onboarding of new team members, and overall productivity.

Integrating with Tools

Leveraging Visual Studio’s Log Message Formatting

One of the primary ways to ensure smooth integration with tools like Visual Studio is through the consistent formatting of log messages. Visual Studio, in tandem with MSBuild, has the capability to recognize and categorize error and warning messages based on their formatting. When this format is adhered to, Visual Studio can directly hyperlink the developer to the offending line of code, enhancing the debugging experience.

See this Microsoft Blog Post [here] and [here].

Reference from the provided Microsoft Blog Post showcases that messages should be structured in the following manner for optimal integration:

{ filename(line-number [, column-number]) | tool-name } : [ any-text ] {error | warning} code-type-and-number : localizable-string [ any-text ]

Examples:

  1. main.cpp(47,5) : error C2065: 'x': undeclared identifier
  2. compileTool : warning CT1014: Variable 'y' might not be initialized
  3. linker : error LNK1104: cannot open file 'example.lib'

In these examples:

  • The file name, line number, and, optionally, the column number are provided at the beginning, making it easy for the developer to locate the issue.
  • The type of message (error or warning) is specified.
  • A code indicating the type and number of the error or warning is provided.
  • A clear, localizable message gives insight into the nature of the problem.

Other Developer Tool Integrations

  1. Javadoc & Doxygen: These documentation generation tools extract comments from source code and turn them into comprehensive API documentation. By following certain commenting conventions (e.g., using /** ... */ in Java or specific tags like @param), developers can ensure that these tools generate detailed and well-structured documentation.
  2. Linters: Tools like eslint for JavaScript or pylint for Python, look for specific naming conventions and code patterns to ensure code quality. By following community-accepted standards and naming conventions, developers can leverage linters to their fullest potential.
  3. Git: While Git itself is not picky about naming, many CI/CD tools that integrate with Git look for specific branch naming conventions. For example, naming a branch feature/XYZ or bugfix/ABC can trigger specific CI/CD pipelines optimized for feature testing or bugfix deployment.
  4. Package Managers: Tools like npm for JavaScript or pip for Python often rely on standard naming and structuring (like package.json or requirements.txt) to manage dependencies. Following these conventions ensures smooth dependency resolution and package updates.

Adhering to established naming conventions and structures improves the clarity and readability of code and allows developers to seamlessly integrate with a plethora of development tools, enhancing productivity and reducing friction in the development process.

Adhering to Conventions and Best Practices

Consistency in naming and structuring code is paramount to ensuring that the software is maintainable, readable, and easily understandable by both developers and the tools they utilize. This consistency can be achieved by adhering to widely recognized conventions and best practices. When a standard exists, use it.

When a standard exists, use it.

C++ File Naming Conventions

When working with C++ projects, certain conventions govern how files should be named. These conventions, while not strictly enforced by the language, provide consistency and clarity across projects:

  1. Header Files: Typically have the .h or .hpp extensions. Use .hxx for auto-generated header files.
  2. Source Files: Typically have the .cpp extension. Use .cxx for auto-generated implementation files.
    • Example: myclass.cpp
  3. Inline Implementations: We might use the .inl extension.
    • Example: myclass.inl
  4. Class Names & File Names: It’s a good practice to have the file name reflect the class, function, or namespace it contains.
    • Example: For a class named Car, the files might be named car.h and car.cpp.

REST APIs Naming Conventions

For RESTful APIs, it’s essential to follow naming conventions that are clear, concise, and convey the API’s functionality without ambiguity:

  1. Use Nouns for Resource Names: Resource names should be nouns, and they should be pluralized.
    • Example: /users instead of /getUsers or /user
  2. HTTP Methods Indicate Actions: Instead of putting verbs in the URL, use HTTP methods.
    • GET /orders: Retrieves a list of orders.
    • POST /orders: Creates a new order.
  3. Consistent Path Structure: Collections should be named using plural nouns.
    • Example: /products/{id}/reviews to get reviews for a product.
  4. Use Lowercase: Resource names and methods should be lowercase.
    • Example: /orders instead of /Orders
  5. Versioning: API versioning can be indicated using a v prefix followed by the version number.
    • Example: /v1/users
  6. Avoid Underscores: Instead, favor hyphen (-) since they are more visually clear.
    • Example: /health-check instead of /health_check

Here are several widely recognized style guides for C++, REST APIs, and Python, along with tools that help ensure adherence to these styles.

C++ Style Guides

  1. Google C++ Style Guide: An extensive guide detailing Google’s C++ coding standards.
  2. Mozilla C++ Style Guide: Provides recommendations for coding in C++ within Mozilla projects.
  3. LLVM Coding Standards: Guidelines for code written for the LLVM Compiler Infrastructure project.

REST API Style Guides:

  1. Microsoft REST API Guidelines: Offers detailed advice for developing RESTful services.
  2. White House Web API Standards: Defines standards and best practices for web APIs.
  3. Zalando’s RESTful API and Event Scheme Guidelines: Contains a wide range of topics related to designing and building robust, developer-friendly APIs.

Python Style Guides:

  1. PEP 8: The most referenced Python style guide that provides conventions for writing readable code.
  2. Google Python Style Guide: Describes the conventions followed in Python code at Google.
  3. The Hitchhiker’s Guide to Python!: Offers a best practice handbook for writing Python code.

Java Style Guides

  1. Oracle: Oracle has online documentation for Java style and naming conventions.

Linting Tools

  1. C++
    • cpplint: A tool for checking C++ code against Google’s style guide.
    • clang-tidy: An extensible C++ linting tool that can check against the LLVM coding standards or any other targeted standards.
  2. REST API
    • Swagger Editor: Provides a user-friendly interface to validate Swagger/OpenAPI definition files.
    • Postman: A platform that can be used for testing and linting REST APIs.
  3. Python
    • pylint: A Python static code analysis tool that checks if a module satisfies a coding standard.
    • flake8: A tool that wraps around pylint, pyflakes, and pycodestyle to check your code base against the coding style.

Internationally Standardized Naming Conventions

  1. ISO 8601 – Date and Time Format
    • Description: Standard covering the representation of dates and times.
    • Example: 2023-10-24 for date and 2023-10-24T14:30:00Z for datetime in UTC.
  2. IEEE 1278.1 DIS Enumeration
    • Description: The Distributed Interactive Simulation (DIS) protocol’s enumeration standard.
    • Example: An entity type enumeration might be 1-1-225-1-1-0 for a U.S. Attack/Trainer Helicopter.
  3. ISO 639 – Codes for the Representation of Names of Languages
    • Description: A set of standards by which languages are assigned unique identifiers.
    • Example: en for English, fr for French.
  4. ISO 3166 – Country Codes
    • Description: A standard for country abbreviations.
    • Example: US for United States, GB for United Kingdom.
  5. ISO/IEC 5218 – Human sexes
    • Description: A standard representing human sexes through a numeric code.
    • Example: 1 for male, 2 for female.
  6. RFC 4122 – Universally Unique Identifier (UUID)
    • Description: A protocol from the Internet Engineering Task Force (IETF) for the format of UUIDs.
    • Example: 550e8400-e29b-41d4-a716-446655440000.
  7. ISO 4217 – Currency Codes
    • Description: A standard for three-letter abbreviations for currencies.
    • Example: USD for United States Dollar, EUR for Euro.
  8. RFC 3986 – Uniform Resource Identifier (URI)
    • Description: A standard defining the structure of Uniform Resource Identifiers (URI), which are a generic set of names/locators for resources on the Internet.
    • Example: https://www.example.com:8080/path/to/resource?query=param#fragment.
  9. E.164 – International Public Telecommunication Numbering Plan
    • Description: An ITU-T recommendation defining the format for international telephone numbers.
    • Example: +1-555-1234567.
  10. ISO 6346 – Freight Container Coding System
    • Description: A standard for the coding, identification, and marking of freight containers.
    • Example: MSKU1234565 where MSKU is the owner code.
  11. ISO/IEC 7810 – Identification cards — Physical characteristics
    • Description: Defines the physical characteristics for identification cards including size, flexibility, layout, etc.
    • Example: The ID-1 format which defines the size and layout for credit cards.

These style guides and tools are invaluable resources to maintain code consistency and quality, helping teams to collaborate more efficiently and produce more maintainable, robust, and reliable software.

By following the conventions and best practices established by the community, developers can ensure their codebases are maintainable and that their APIs are intuitive for consumers. These conventions also pave the way for smoother integrations with tools, reducing potential roadblocks and ambiguities during development and deployment.

Summary and Final Thoughts

Effective naming plays a pivotal role in ensuring readability, maintainability, and clarity. This guide covered many naming conventions’ nuances, exploring foundational principles and advanced techniques. Beginning with creating schemas and the importance of taxonomic ranking, we ventured into machine readability, ensuring names are pronounceable and avoiding context duplication. We also covered the symmetry between various domains like Jira issues and Git branches and underscored the significance of adhering to conventions for C++ and REST APIs. Drawing from reputable style guides and integrating tool-specific conventions, this guide is a holistic resource for new and seasoned developers. By adhering to these guidelines, one can ensure their codebase remains lucid, scalable, and, most importantly, understandable for all collaborators!

The Evolution of Naming Practices: A Personal Anecdote

Most of the time, what we need are good instincts when it comes to naming. Spending time contemplating naming standards, conventions, and rules is usually overkill. Day-to-day, you will get names wrong. Refactor when necessary. What you should avoid is whiteboarding a naming schema for something trivial. Just like with name creation, the amount of effort you put in should be relative to the scope of the problem.

Early, I was not concerned with what my function parameter names were. I called them the thing that they were by default:

/// Function signatures, obvious but unhandy names.
/// \param temp The temperature in SI units (Celcius)
void setCelciusTemperature(double tempC);

/// Function signatures, obvious but unhandy names.
/// \param tempF The temperature in Farenheight
void setFarenheightTemperature(double tempF);

/// Function signatures, obvious but unhandy names.
/// \param fname The name of the file.
void setFileName(std::string fname);

Later, I found that it would support copy/paste better (of both function signatures and internal code) by standardizing functions taking one parameter, always calling that parameter x. This took time for me to evolve into (years), but I really appreciate this now. I no longer put ANY thought into what I call a function parameter; my function signatures are easier to copy around, and my documentation is easier to write. These are all very small wins, but I’m grateful for them. That said, it was not worth my time as a junior engineer to spend time contemplating function parameter names. I had many more important things to learn. I hope to give you this lesson and let you leapfrog my learning and get on with more important problems.

/// Function signatures and APIs evolved.
/// \param x The temperature in SI units (Celcius)
void setTemperature(double x);

/// Function signatures evolved.
/// \param x The name of the file.
void setFileName(std::string x);

  1. There are many variations on this classic quote. The first place anyone found this quote online was in Tim Bray’s blog. Tim said that he first heard it around 1996-7. Thanks to Martin Fowler for the research on this. ↩︎
  2. How do we pronounce std:: in the C++ world? Some people say, “Stood.” I just say “Standard.” A three-letter namespace is great for something used so often, but this might devolve into a “Gif” vs. “Jif” debate. ↩︎

2 Replies to “Naming Things – A Comprehensive Guide”

Leave a Reply

Related Posts