DEV Community

Cover image for Best practices to write a readable and maintainable code | Choosing meaningful names
Thiago Pacheco
Thiago Pacheco

Posted on • Updated on

Best practices to write a readable and maintainable code | Choosing meaningful names

They say one of the most difficult tasks as a software developer is naming things but it is also one of the most important practices to have meaningful names when defining variables, functions, classes and everything in between.
Following good practices and naming conventions can make your code more readable and maintainable, and it most certainly will make it easier for other people to understand and work with it.

But how can we do that? What are the good and bad practices when naming things in software development?

I like to think of coding almost as writing a book, where I would try to guide the reader to follow a clear structure with meaningful names that helps them to stay on the right path of what they are looking for.

One suggestion that I like to follow when coding is to pause and think "What is my motivation to create this?". That makes you focus on the bigger picture other than the immediate problem-solving mindset according to the book The Pragmatic Programmer by David Thomas and Andrew Hunt (excellent read by the way).

Following up on the same reference mentioned above, there is a great example of why naming things right matters.
Let’s do a quick exercise to see this in practice:
Take a look at the image below and then follow the quick instructions, it won’t take more than a minute, I promise!

Read the colors out loud

  1. Say the words out loud as they are written.

  2. Now say the words out loud according to their colour.

Even if you succeeded in the second step, you probably had to put more effort into it, didn’t you?
This is just a quick example to show that what is written takes precedence in our brains, even if the context around it means a different thing.

Now that we have an idea of how important it is to name things right, let’s take a look at some strategies:

👀 Naming variables

Variables are the smallest piece but usually the most significant since they are the base composition of the entire application. Naming them wrong may feel like a domino effect, where all the subsequent variables rely on the wrong meaning of previous ones.
Let’s look at some practices to avoid that:

🔍 Avoid abbreviations:

Abbreviations may help with making your code smaller, but they make it extra complicated to understand the meaning of the variable in place. Unless you are working in a very constrained environment like embedded systems you should consider avoiding the abbreviations.
Abbreviations may seem intuitive to the person writing the code and even to people that have a clear understanding of the product but it may be a big blocker for people reading it for the first time (sometimes even second, third, …)

📜 Avoid generic names

Many programs use generic names on variables, sometimes because of patterns we learn early in our careers and keep using it like for i of items.
Other times it is just because we are trying to keep the code somehow generic to facilitate extending it. No matter what the reasoning is, consider using clear and specific names other than super generic ones, unless you don’t have a choice.
The problem with generic names is that it doesn’t represent what the current piece of code is supposed to do because generic names usually bring also a sense of ambiguity.
Let’s take a look at a quick example:

# User-type employee
user = User("Jon", "Snow", type="employee")


Enter fullscreen mode Exit fullscreen mode

In the example above, if your codebase manages multiple types of users it may be tricky to know all possible paths every function needs to follow/check. That will force you to read the whole function to see what can happen for each type of user. It would be way better to be able to assume what a function does just by reading its signature, wouldn't it?

To avoid this problem, first, make sure that the variable name reflects the intention of what it is being used for. Next, double-check that it has a clear and unique meaning of what it represents.

Replace by:

employee = User("Jon", "Snow", type="employee")
Enter fullscreen mode Exit fullscreen mode

🚽 Misleading names

This is a very scary one where it usually happens during maintenance, refactoring or change in the codebase where someone accidentally forgets to rename the variable right after changing what it represents.
This is extremely harmful because it guides the next readers directly to a misinterpretation of the code. Therefore it requires a lot of effort to understand, and maintain and it is usually the reason why weird and complicated comments and documentations exist.

Documentations are a required and really important step though, don't get me wrong! But having very verbose documentation only to explain poorly named code or misleading names is a waste of time both for the person writing it and for those who are going to read it later.

To avoid this issue, make sure that whenever you change a code you also rename the variables according to their context. Follow the first steps described here when applying code changes too, chances are you might not only avoid misleading names but also fix existing poorly named variables in place.

💊 Keep it short but meaningful

it is always good to have a very short variable name and we should aim for that as long as it represents what it means. But keep in mind that meaningful names take precedence over short names, so try to focus on the meaning first.

🔫 Naming functions

All of the suggestions about variables also apply here when naming functions. By following that, it will help you to describe the right context that the function represents.
But on top of that, other than providing a clear context we also need to explicitly show what action this function is supposed to perform.

Here are some quick suggestions:

  • Functions should use verbs and be clear about what action it performs;
  • Handle only one single action;
  • Use clear parameter names.

💉 Prefix suggestions

Here is a quick reference about what prefix you can use to explicitly describe the intention of your function:

Action Prefixes Description Examples
Retrieve data get, find, show, list Used when you need to retrieve data get_users, find_employees, show_user_details, list_comments
Insert data insert, add Used when you intend to add new information add_user, insert_user
Update data update, change Used when you want to update information update_user, change_username
Update or insert set Used when you wish to update or create a new record if it doesn't exist set_days_off
Retrieve from 3rd party fetch, retrieve Used when you wish to fetch data from a third-party resource, like over HTTP request for example fetch_users, retrieve_comments

Keep in mind that the table above is just a set of suggestions, not hard rules to be followed. I found out over the years that these are the most common prefix conventions for naming functions but, each company have their best practices and chances are you already follow some specific guideline.

📌 Functions should handle a single action

Be sure that your function does not do multiple actions.
Whenever your function contains an and in the name or anything that reminds you that you most certainly are doing many things and you should consider splitting it up into multiple functions.

def update_user_and_send_notification():
Enter fullscreen mode Exit fullscreen mode

The format above just makes it too verbose and therefore less readable in most cases.

Consider splitting it up:

def update_user():

def notify_user_updated():
Enter fullscreen mode Exit fullscreen mode

But what if they need to be executed in sequence?
No problem, just create a function that acts as an aggregator

def update_user_handler():
    # first
    # then
Enter fullscreen mode Exit fullscreen mode

Usually the definition proposed above with handler is used on a layer that is exposed to outside callers on your application, like a service or an API layer. See more below in Naming modules/packages.

📌 Function parameters:

When dealing with functions it is very common to see very generic parameters and that can also be a big problem. whenever you have a poorly named function parameter it would result in the same problems we talked about in the naming variables section.

Let's look at the following example:

update_user(id, data: dict):
Enter fullscreen mode Exit fullscreen mode

The parameter data doesn't clearly state what is being updated in the user instance.
Is it the username? Age? Is it every single attribute?
You won't be able to know unless you read the whole function and see what the code does.

To avoid that, be explicit about what attributes the function is supposed to be handling changes.
One way to do that is to list every attribute as a separate parameter of the function:

def update_user(id: int, name: str, age: int):
Enter fullscreen mode Exit fullscreen mode

If the list of attributes is too long, consider the use of a DTO (Data Transfer Object) or a similar strategy to make the function readable but still specific.

def update_user(id: int, user_data: UpdateUserData):

class UpdateUserData:
    name: str
    age: int
Enter fullscreen mode Exit fullscreen mode

Quick tip: Don't forget to explicitly define typing hints if you are using a dynamically typed language such as Python or Typescript for example. That makes it much easier to understand what the function does just by reading its declaration.

✏️ Naming methods

Naming methods is very similar to naming functions, the main difference here is that the context is somewhat explicit already, since you are evoking the method from a specific class.

For example, an isolated function to ADD a new user can be defined as the one below:

def add_user(user: User):
Enter fullscreen mode Exit fullscreen mode

When defining the same functionality through a method of a UserRepository class, for example, you can ignore the context part of the name, since it is already implicit by the class name:

class UserRepository:

    def add(user: User):
Enter fullscreen mode Exit fullscreen mode

📁 Naming classes

When naming classes you should keep in mind what is the main purpose of the class that you are creating. If you find that the class is too broad and can be used to manage multiple contexts, consider rethinking it and splitting it up. Defining too broad and long classes is one of the reasons why generic names are applied here and we should try to avoid that.

The most straightforward class definitions are the ones that represent a domain model, like a User, Client, Article, etc. But we know that programs never stop there and we always stumble on broad classes every now and then.

Here are some quick suggestions:

  • Keep it simple and descriptive;
  • Use a noun
  • Use CamelCase (or your preferred language default)
  • Use whole words and avoid acronyms and abbreviations

📗 Naming abstractions/interfaces

Abstractions are a great way to make your code decoupled and testable, but naming it wrong may only complicate things.

Here are some quick suggestions to follow:

  • Use CamelCase names like classes
  • Clearly state that this is an abstraction/interface according to your preferred language:
    • E.g. AbstractDBSession
  • It should represent a clear context or group of contexts:
    • E.g. AbstractUser, AbstractClient, AbstractRepository

📙 Naming enums

First of all, when defining enums make sure that you need it. If your enum contains only a single value it should be a constant or even a simple boolean attribute.

class UserStatus(enum.Enum):
    ACTIVE = 'active'

class User:
    status: UserStatus

# The code above could be a simple boolean attribute of the user object
# E.g.

class User:
    is_active: bool = True
Enter fullscreen mode Exit fullscreen mode

Now that you confirmed that you need an enum, you can apply the same suggestions of classes and variables definitions here.

  • Use a noun
  • Be clear about in what context it should be used:
    • E.g. UserStatus, UserType, AccountType
  • Make it short and meaningful

📂 Naming modules

Module names should represent a group of functionalities that belong to the same general context. Your modules may vary a lot based on your own company's guidelines but some general rules can be applied to make sure that your package name makes sense.

One great suggestion I like to follow is the one found on Google's python styleguide, where you should be able to call a public code from a package using its full path, for example:

from my_app import domain

user = domain.User("Jon" , "Snow")
Enter fullscreen mode Exit fullscreen mode
from import user_handlers

users = user_handlers.get_all()
Enter fullscreen mode Exit fullscreen mode

If you can follow the example above and it is easy to follow what the code does and where it comes from, then you can confirm that your package name is clear enough.

Every language, framework and even companies have their set of guidelines to name things, and the goal is to make sure that we are writing code that is going to be easy to understand by other people and our future selves.
Next time you code, try to see yourself as an author and apply these practices to guide you through the process. Your future self and every reader of your code will be thankful for that!

Thanks for making to the end, leave a like and comment if the article was helpful to you!

Top comments (6)

vulcanwm profile image

this is really helpful!

andrewharpin profile image
Andrew Harpin

I relate to this so much, I have a bunch of new engineers join the team.

Getting to unlearn the crap they've been taught in their programming classes is hard.

Some useful examples here

pacheco profile image
Thiago Pacheco

Thank you for the feedback!
Yes, onboarding new engineers is always a challenge so trying to define a styleguide or using some existing one like Google's can be very helpful. I hope the tips here can provide some initial thoughts about that! :)

rfaco profile image
Roger Faco • Edited

This should be mandatory.
Awesome content.

Thanks, Thiago.

pacheco profile image
Thiago Pacheco

Thanks Roger!

colematt profile image
Matthew Cole

Abbreviations may help with making your code smaller, but they make it extra complicated to understand the meaning of the variable in place

This advice is contrary to the Linux kernel coding style, which notes “C is a Spartan language, and so should your naming be. … C programmers do not use cute names like ThisVariableIsATemporaryCounter. A C programmer would call that variable tmp, which is much easier to write, and not the least more difficult to understand. … Local variable names should be short, and to the point. If you have some random integer loop counter, it should probably be called i. Calling it loop_counter is non-productive, if there is no chance of it being mis-understood.”

I understand that a lot of this post basically devolves to “this is my opinion, and these are the reasons for those opinions”. That’s fine. For what it’s worth, I agree with a lot of them! But perhaps the first piece of advice should be “find your project’s style guide, and frequently use a beautifying tool that can be configured to your project’s style guide.”

Unless you are working in a very constrained environment like embedded systems you should consider avoiding the abbreviations.

The length of the variable name has little to no impact on resource consumption in embedded systems since the variable names don’t cross the semantic gap in the compilation phase.