Practical Clean Coding: Naming

This article is part of the Practical Clean Coding series.

There are only two hard things in Computer Science: cache invalidation and naming things.

— Phil Karlton

We write programs for humans, not computers.

Thinking the opposite is a common misconception, or we don’t think hard enough about it.

Photo: Luis Quintero: https://www.pexels.com/tr-tr/fotograf/cete-sandalyelerinde-oturan-insanlar-2774556/

Who is the target audience of the code?

Let’s think about it.

The machines can understand the machine code and work most efficiently with it, speaking roughly. When we write code in a higher-level language, our code is either transformed into lower-level languages or interpreted by a dedicated program. We introduce more abstraction levels in each case on top of the simple machine code. This will introduce more complexity and redundant code or operations during the transformation or interpretation processes. So, why do we write code in a higher-level language?

The answer is simple: We write the code for humans.

We want to make our code understandable to humans.

We want to be able to maintain that code after six months when we completely forget what it was about.

We want to understand the code that we inherited from someone else. We must understand it to modify it properly and adopt new changes and requirements.

In short, we want our code to be readable, understandable, and maintainable.

Photo: Catarina Sousa: https://www.pexels.com/tr-tr/fotograf/avustralya-haritasi-68704/

Programming as a mental model

If we agree that we are writing code for humans, let’s consider how to write better code for humans.

We create a mental model of the world in our minds by programming. It is not necessarily the real world, but at least we can say it is a simplification of a more complex problem domain. So, we want the model to be as accurate as possible while still being simple enough to work with.

Since the model “models” the real world, we need to map or translate the concepts in our minds from the code to the real-world equivalents. This is similar to speaking a non-native language.

If you can not think in that foreign language, you will naturally translate the words and sentences in your mind. Since you are doing more work, speaking in a foreign language will take more time and energy for you. But, if you learn to think in a foreign language directly, no translation will be involved in your mind, so you will be more fluent and spend less energy while speaking.

Applying this metaphor to programming, if we can easily map concepts in the code to the real world, it will be easier to understand and reason about the code.

Guess what is the very first thing to do to make code-to-world mapping easier? It is naming.

We should stop being careless about naming things in our code. It is the first and most important step to clean coding practices.

Why is that? Why is naming things the most important ingredient of a clean mental model?

Meaningless names

Imagine you have inherited a code with lousy naming. Variables, functions, and classes have misleading or hard-to-read names. You will feel lost, right? You will need guidance from someone who knows the code better. Your team must spend more man/hours onboarding new team members. On the other hand, having good names is like having a guide while navigating the code.

Do you understand what this code does?

public class P {
private int a;
private int b;
private List<Integer> l;

public P(int x, int y) {
this.a = x;
this.b = y;
this.l = new ArrayList<>();
}

public void m() {
for (int i = 0; i < a; i++) {
int t = f(i);
if (g(t)) {
l.add(t);
}
}
s();
}

private int f(int n) {
return n * b;
}

private boolean g(int v) {
return v % 2 == 0;
}

private void s() {
Collections.sort(l);
}
}

Nevermind. It isn’t worth it. But you get the idea.

Brainteaser where color names written in different colors, so it is hard to say the actual color of the text.

Source: https://www.businessinsider.com/game-shows-words-in-different-colors-stroop-effect-it-2019-7

Misleading names

If there is something worse than not understanding code at all, it is understanding it wrongly. Poor naming can cause you to misinterpret the meaning of the code, causing unexpected bugs and unnecessary debugging time. Trust me, this is not the best use of your valuable time.

Let’s check this example:

public class UserManager {
    private List<User> deletedUsers;

    public UserManager() {
        this.deletedUsers = new ArrayList<>();
    }

    public void deleteUser(User user) {
        deletedUsers.add(user);
    }

    public void removeInactiveUsers() {
        for (User user : deletedUsers) {
            if (!user.isActive()) {
                user.deactivate();
            }
        }
    }

    public int getActiveUserCount() {
        return deletedUsers.size();
    }
}

Poor naming makes it hard to tell the purpose of the methods. It doesn’t make sense at all. There are several naming problems:

  • deletedUsers list contains all users, not just the deleted ones.
  • deleteUser method doesn’t delete but adds the user to the list.
  • removeInactiveUsers doesn’t remove users, deactivates them.
  • getActiveUserCount returns the total number of users, not just the active ones.

After fixing the naming, we will get this:

public class UserManager {
    private List<User> allUsers;

    public UserManager() {
        this.allUsers = new ArrayList<>();
    }

    public void addUser(User user) {
        allUsers.add(user);
    }

    public void deactivateInactiveUsers() {
        for (User user : allUsers) {
            if (!user.isActive()) {
                user.deactivate();
            }
        }
    }

    public void removeDeactivatedUsers() {
        allUsers.removeIf(user -> !user.isActive());
    }

    public int getTotalUserCount() {
        return allUsers.size();
    }

    public int getActiveUserCount() {
        return (int) allUsers.stream().filter(User::isActive).count();
    }
}

How to give better names?

1. Be Specific and Descriptive

We should use names to describe the behavior or data instead of using generic names. This will reduce the need for comments. It is another topic, but you should avoid comments unless necessary because your code should explain itself as much as possible.

In the following example, it is not clear what the input data is and what the process method does exactly. So, the developer might need to put some comments on this method to explain the context better. Instead, they should rename the method and the parameter as below.

 // BAD! Don't do this!
void process(List<String> data) 

 // BETTER!
void calculateAverageUserRatings(List<String> userRatings)

2. Use Consistent Naming Conventions

All modern languages have a well-established writing style. Stick to that also for naming.

Bad:

class user_account {
    private String UserName;
    private String email_address;
    private int Age;
}

Good:

class UserAccount {
    private String username;
    private String emailAddress;
    private int age;
}

3. Avoid Unnecessary Abbreviations

There is almost no reason to use uncommon abbreviations in your code. It will make the code less understandable.

Bad:

int d; // Days
List<String> lst; // List of items

Good:

int daysElapsed;
List<String> shoppingItems;

4. Use Domain-Specific Language

Instead of using names that reflect the technical details of the code, like “Form,” “List,” etc., use the domain language that better reflects the mental model we discussed earlier.

Bad:

class Form {
    String i1;
    String i2;
    Date i3;
}

Good:

class PatientRecord {
    String patientName;
    String diagnosis;
    Date admissionDate;
}

5. Avoid Redundancy

Avoid unnecessary repetitions in naming. It might sound counter-intuitive to “avoid unnecessary abbreviations” rule, but it is not. Everything should have a balance. Too short is bad, and too long is bad.

In the following example, we don’t need to repeat the word “customer” in the field names because it is obvious from the context.

Bad:

class Customer {
    String customerName;
    String customerAddress;
    int customerAge;
}

Good:

class Customer {
    String name;
    String address;
    int age;
}

6. Use Opposites for Complementary Operations

Think about the overall picture, not just the single item namings. It will make your code more consistent.

For example, at first glance, it is not obvious if init/finish are opposite or symmetrical operations. But it is easy to say open/close, or start/stop are opposite operations.

Bad:

file.init();
file.finish();

Good:

file.open();
file.close();

7. Use Verbs for Functions and Nouns for Variables/Classes

This is a very fundamental rule, but it is worth mentioning anyway. Using verbs for behavior and nouns for data/things would be best. As with all rules, there are some exceptions to this rule, like builders or DSL functions, but let’s put those exceptions aside for now.

Bad:

class calculation {
    int number(int x, int y) {
        return x + y;
    }
}

Good:

class Calculator {
    int add(int firstNumber, int secondNumber) {
        return firstNumber + secondNumber;
    }
}

8. Boolean Naming

Since the ultimate goal is to make the code more readable like plain English, name boolean-returning methods with “be” verbs. This way, it will be read more smoothly and naturally at the use-site.

Bad:

boolean flag = user.ValidateUserCredentials();

Good:

boolean isValid = user.areCredentialsValid();

9. Avoid Misleading Names

As discussed earlier, using misleading names is the worst! Take special care to keep names true, and do not allow them to lie to you or a colleague.

Bad:

List<User> inactiveUsers = getAllUsers();

Good:

List<User> allUsers = getAllUsers();

Conclusion

We usually don’t put enough effort into finding better descriptive names while coding since we are in a rush or don’t care much. But it is really important to give good names.

Proper naming in programming is a crucial skill that significantly impacts code quality, readability, and maintainability. Good names will bring various benefits:

  1. Reduce cognitive load for yourself and other developers
  2. Minimize the risk of misunderstandings and bugs
  3. Make your code self-documenting, reducing the need for excessive comments
  4. Facilitate easier code reviews and knowledge transfer within your team
  5. Improve the overall design of your software by forcing you to think clearly about each component’s purpose

Good naming is an ongoing process. Refactor and improve names as your understanding of the problem domain evolves. This pays off in the long run through reduced debugging time, easier maintenance, and smoother collaboration.

Ultimately, aim to make your code tell a story. When a new developer reads your code, they should be able to understand its purpose and functionality without extensive external documentation. By giving better names, you’re not just writing code; you’re crafting a narrative that guides future readers (including yourself) through your software’s logic and intent.

Take a moment right now to look at your current project. Find one poorly named variable, function, or class and rename it using our discussed principles. This small action is your first step towards cleaner, more readable code.

Remember, better naming is a skill that improves with practice. Keep at it, one name at a time.