Naming Your Code

Introduction

In this post, we will explore the importance of naming in your code, encompassing functions, parameters, variables—essentially anything that requires a name.

Many junior developers often struggle with effective naming. Our goal here is to provide guidelines and best practices for creating meaningful names.

Why Naming is Important

The primary reason naming is crucial is maintainability. When you revisit your code after a few months or someone else examines it for the first time, it’s essential that the code is easy to understand without needing to decipher the underlying logic first. Good naming practices ensure that anyone can maintain the code, not just the original author.

Separation of Concerns

Proper naming also aids in the separation of concerns. A name defines the operational context, making it clear whether a new addition fits within that context. For instance, a class representing a person should not include data or actions related to transportation, even though a person can commute. These concepts should be managed by separate parts of the code.

Formulating a Good Naming Convention

The key question isn’t why you need a good naming convention but how to develop one.

Before we dive into the specifics, note that this example uses snake case for readability. However, you should use the appropriate case style for your programming language, whether it’s PascalCase, camelCase, or snake_case.

Context

Naming in programming heavily relies on context. By asking a few key questions, we can determine the most appropriate names for different elements:

  • What is this?
  • What does this entity represent?
  • What is its purpose?
  • What does it do?

For example, if we have a structure that represents an individual, it should be called “person.” A collection of such structures should be named “people” because it represents multiple “person” objects.

Rules for Naming

Grammar

Names should follow proper grammar rules. Since we use grammar daily, applying it in code makes the code more intuitively understandable. For instance, the singular “person” and the plural “people” follow standard grammar rules, making their meanings clear.

Association

Naming should also create a clear association with real-world entities and concepts. By mirroring real-world terminology in code, we make the code more relatable and less confusing. For instance, a “person” in code should represent a person in real life. Misalignments between real-world associations and code can lead to confusion.

This principle extends to actions as well. For example, the term “add” means to increase or contribute to a collection in the real world. In programming, “add” should carry the same meaning, ensuring consistency and clarity.

Verbs

To that end, a function name should clearly indicate the action being performed and the context in which it operates. Sometimes, the context is implied by the verb itself. Here are some examples to illustrate this concept. Remember, the importance of a name is twofold:

  1. Where the entity is declared.
  2. Where the entity is used – This aspect is often overlooked, but good naming practices are crucial for conveying intent to anyone observing the code in use.

Examples

Simple Verb

  • add: If we are working with numeric values, we assume this function will add the numbers together and produce a numeric result.

Contextual Verb

  • add_float: In some cases, we want to provide more context. This name specifies that we are adding float values, and the result will also be a float. When you see a function called “add_float,” it is immediately clear that the function is adding numbers and that those numbers are floats. You might think “add” is sufficient and that the parameters would define the context, but when reading the usage, the parameter types might not be immediately obvious. By naming the function “add_float,” you eliminate the need to know the parameter types; the intent is clear from the name alone. Let’s call this “information at a glance.”

At a glance, it should be clear what the intended behavior of the code is. The quicker you understand the intent of the code, the quicker you can determine if it is behaving as expected.

Naming Rules

  • Functions should start with a verb: Since functions perform actions, their names should begin with a verb that describes what work the function is doing.

Accepted terms

In the programming industry, certain terms are universally accepted and can be applied across various languages. These terms help in creating a common understanding and improve code readability. Here are some widely accepted terms:

  • get: Retrieve a value from an object for a given name. For example, get_first_name. Sometimes this involves a function call that fetches the value from an existing structure, e.g., let device = get_device(device_name).
  • set: Assign a value to an object for a given name. For example, set_first_name.
  • save: Store data or state. For example, save_document or save_progress.
  • load: Load a resource from the device where the code is running. For example, load_png indicates loading a resource from an image (PNG) file.
  • sync: Synchronize data between sources. For example, sync_calendar or sync_contacts.
  • fetch: Retrieve a resource over a network. For example, fetch_png indicates fetching a PNG resource from an external device like a service or application server.
  • generate: Construct data, code, resources, etc. This can refer to creating a single item or multiple items.
  • Single: generate_uuid
  • Multiple: generate_random_people
  • Generating something is often the task of a factory function.
  • build: Similar to generate, but implies constructing a single item with variable input. For instance, let house = build_house().set_brick_type(face_brick).build().
  • create: creating a new instance of a object or resource. For example create_person
  • add: Add something to a collection. Terms like “push” are not universally used across all languages, whereas “add” is simple and widely understood.
  • remove: Delete an item from a collection. “Delete” is less commonly used in spoken language in this context, making “remove” a clearer choice.
  • clear: Remove all items from a container. In spoken language, to clear something out means to empty it completely.
  • delete: Removing a object or resource. For example, delete_account
  • replace: In a collection, remove a specified object and substitute it with a new one.
  • modify: Change properties of an object while leaving the original object in place.
  • notify: Inform other parts of the system that changes have occurred to an object. For example, notify_first_name_changed.
  • validate: Check if data or conditions meet required criteria. For example, validate_email.
  • calculate: Perform a computation and return a result. For example, calculate_total.
  • process: Perform a series of operations on data. For example, process_order or process_payment.
  • subscribe: Register to receive updates or data from a source.
  • unsubscribe: Deregister from receiving updates or data.
  • connect: Establish a connection to a service or resource. For example, connect_to_database or connect_to_server.
  • disconnect: Terminate a connection to a service or resource. For example, disconnect_from_database or disconnect_from_server.
  • merge: Combine data from multiple sources. For example, merge_files or merge_lists.
  • split: Divide data into parts. For example, split_string or split_list.

Naming Rule

A simple rule to follow is: use commonly used spoken language when naming entities in code. This practice enhances clarity and ensures that the code is more intuitive and easier to understand for everyone involved.

Descriptors

Descriptors add clarity by providing additional information about the named entity. For example, in notify_first_name_changed, the descriptor “changed” tells us what happened to first_name.

Common Descriptors

  • changed
  • added
  • removed
  • failed
  • succeeded
  • validated
  • expired
  • saved

These descriptors aren’t exhaustive but illustrate the purpose: to enrich the context so the receiver understands how to respond appropriately. This practice enhances code transparency, making it clear why a particular action or notification is taking place.

Identifiers

When naming constants, variables, classes, structures, and similar entities, the name should clearly identify what the entity is. Avoid using single-character names like “i,” “x,” or “y.” Although these are historically used in looping operations in computer science, they lack clarity.

Naming Conventions for Loops

  • Descriptive Variable Names: When looping through a collection of “people,” use a descriptive identifier like “person.”
  • Index-based Approach: If you need to use an index, name it “index.” However, most modern languages support shorthand methods that eliminate the need for an explicit index, so prefer those methods when possible.

By using descriptive names, your code becomes more readable and understandable, making it easier for others (and yourself) to maintain.

for person of people {
  ...
}

Many developers fall short when it comes to naming variables. They might think, “I need a variable name, so I’ll just use something quick and dirty for now.” This lack of thought can lead to problems later.

The Consequences

Six months down the line, someone else—or even the original developer—tries to understand the code. Because little thought was put into the identifiers, it’s difficult to grasp the context and purpose of the code.

The Solution

Invest time in naming variables clearly and descriptively. This effort pays off by making the code more maintainable and understandable for everyone involved. Proper naming practices prevent confusion and facilitate smoother development and debugging processes.

Crafting Effective Identifiers

When naming identifiers such as variables, it’s important to keep several key principles in mind to ensure clarity, maintainability, and consistency in your code:

  1. Clarity: Names should clearly convey the purpose and meaning of the identifier. The name should be self-explanatory and easy to understand for anyone reading the code.
  2. Descriptive: Use descriptive names that accurately describe what the identifier represents or stores. Avoid generic names or single-character names unless they are widely accepted conventions (e.g., i, j for loop counters in short loops).
  3. Consistency: Follow consistent naming conventions throughout your codebase. When referring to something as “person”, always refer to it as “person”
  4. Avoid Abbreviations: While some abbreviations may be widely understood (like num for number), avoid cryptic abbreviations that might not be clear to others who read your code later.
  5. Contextual Awareness: Consider the context in which the identifier is used. Choose names that make sense within that specific context and provide enough information for someone to understand its purpose without needing additional explanation.
  6. Avoid Overly Long Names: While descriptive names are important, excessively long names can make code harder to read. Aim for a balance between clarity and brevity.
  7. Use Meaningful Prefixes/Suffixes: Sometimes adding a prefix or suffix can clarify the type or role of the variable (e.g., is_ for boolean variables, _list for lists, _count for count variables).
  8. Avoid Reserved Words: Avoid using language-specific reserved words or keywords as identifiers, as this can lead to confusion and potential errors.
  9. Review and Refactor: Periodically review identifier names and refactor them if better names become apparent or if naming conventions change within your team or project.

By adhering to these principles, you can create identifiers that enhance the readability, maintainability, and understanding of your codebase for yourself and others who may work with it in the future.

Common Boolean identifier prefixes

Boolean identifiers often use prefixes to clarify their purpose and state within code. Here are some common prefixes and their usage:

  • is_: Indicates a positive condition or state. For example, is_active denotes whether something is currently active or not.
  • can_: Indicates if something is able to happen based on a certain state. For example, can_connect checks if a connection can be established.
  • has_: Indicates whether something is present or exists. For example, has_name checks if a name is assigned or present.
  • allow_: Indicates whether something is permitted or not. For example, allow_change specifies if a change operation is allowed.
  • should_: Indicates something that is recommended or advisable. For example, should_display indicates whether something should be displayed.
  • need_: Indicates a requirement or necessity. For example, need_approval checks if approval is required.
  • must_: Indicates a mandatory condition or requirement. For example, must_confirm specifies that confirmation is mandatory.
  • will_: Indicates a future action or state. For example, will_execute indicates that an action will be executed.
  • want_: Indicates a preference or desire. For example, want_notifications indicates a preference for receiving notifications.
  • use_: Indicates an option or choice. For example, use_cache specifies whether to use a cache.
  • require_: Indicates a mandatory condition or prerequisite. For example, require_authentication specifies that authentication is mandatory.
  • exclude_: Indicates something that is excluded or not included. For example, exclude_admins specifies that administrators are excluded.
  • avoid_: Indicates something that should be avoided or prevented. For example, avoid_duplicates specifies that duplicates should be avoided.

These prefixes help to provide clear intentions and conditions for boolean variables, making it easier to understand their purpose and usage within the codebase.

Defining data types

When naming identifiers, clarity is paramount. Sometimes, the name alone is sufficient to convey its purpose and type. For instance, a identifiersnamed “age” clearly represents a numeric value without needing additional qualifiers.

However, there are cases where adding extra context enhances clarity. Consider “last_date_modified” as an example. The prefix “last_date” indicates it’s a date, while “modified” provides additional context—it specifies that this date represents when the identifiers was last changed or updated.

By striking a balance between clarity and conciseness, identifiers can effectively communicate their purpose within the code, making it easier for developers to understand and maintain. This approach ensures that each identifier serves as a clear and informative label for its associated data or functionality.

Here are some examples to illustrate how including data types in variable names can be helpful:

  • names_list: Clearly indicates a list containing names.
  • students_count: Specifies a count of students, emphasizing it is a number.
  • departure_date: Clearly specifies a date, likely indicating a departure time.
  • value_string: Indicates a variable storing data as a string type.

Summary

Effective naming in programming is crucial for clarity and maintainability. Names should be clear, descriptive, and consistent throughout the codebase. They should convey the purpose and functionality of variables, functions, classes, and other entities clearly and concisely. Avoid ambiguous or overly abbreviated names, and use meaningful prefixes or suffixes when necessary to provide additional context. Consistency in naming style and conventions helps improve readability and understanding for developers working with the code. Periodically reviewing and refining names ensures they remain relevant and understandable as the code evolves. By following these guidelines, you can create code that is easier to read, maintain, and debug.