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:
- Where the entity is declared.
- 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
orsave_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
orsync_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
orprocess_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
orconnect_to_server
. - disconnect: Terminate a connection to a service or resource. For example,
disconnect_from_database
ordisconnect_from_server
. - merge: Combine data from multiple sources. For example,
merge_files
ormerge_lists
. - split: Divide data into parts. For example,
split_string
orsplit_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:
- 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.
- 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). - Consistency: Follow consistent naming conventions throughout your codebase. When referring to something as “person”, always refer to it as “person”
- 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. - 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.
- 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.
- 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). - Avoid Reserved Words: Avoid using language-specific reserved words or keywords as identifiers, as this can lead to confusion and potential errors.
- 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.