Capa do artigo: Minimalist Code: How Variable Names Affect Readability and Performance
Series Minimalist Code · Part 1 of 3

Minimalist Code: How Variable Names Affect Readability and Performance

Series Minimalist Code Part 1 of 3

Today the topic is technical, even if it does not look that way at first glance. What can we say about variables? Their types? The size of each type? How they are allocated and where they are stored? How they behave inside an array or object? How constants are allocated in memory? How immutable variables work? What about overflows? Are there attacks that exploit variable handling? How do operations with those variables happen inside the processor? How do you calculate space usage? How do you optimize computational resource usage through good design?

Those are subjects worth exploring in other articles, because they matter greatly for any software developer. For now, the focus is on something that often gets overlooked: how to name variables.

Have you ever stopped to think about how you choose variable names in your code? Sometimes that choice feels trivial and automatic, but it can make all the difference over time.

This is a common shortcoming in many projects I have worked on, a real blind spot for many developers. Giving variables names that are clear, purposeful, and easy to understand is essential. It makes code more readable, improves modeling, and directly contributes to better quality deliveries.

I mentioned in another post that a good software engineer should always produce two deliveries per task: one for the stakeholders who requested the work, and one for their colleagues, meaning the code in the repository must be of quality. That quality also contributes to a healthier work environment.

But what is the real importance of simply giving a name to a memory space?

"All things are lawful, but not all things are profitable. All things are lawful, but not all things edify." 1 Corinthians 10:23 — Paul of Tarsus

Reducing cognitive effort with efficient variable names

Let us clarify a few points. When we talk about programming languages and variable declarations, we generally encounter two main paradigms: typed and untyped languages. I will not get into which is better, because, as in many situations, there are no perfect solutions, only trade-offs. What matters is understanding which problem you are willing to accept.

"There are no solutions, only trade-offs." — Thomas Sowell

You might be wondering: "Hold on, you started talking about variable names and now you are getting into language typing?" The point is to connect those concepts to code design and to an important quality factor called readability. Translating readability as "legibility" or "readability" does not fully capture the depth of what the term means.

When working with a typed language, declaring a variable involves specifying its type as part of its signature. Why not just use a number, a letter, or an arbitrary symbol for the type? Because good code design depends on choosing good names, which helps reduce cognitive effort and increases code readability. Remember: we spend more time reading code than writing it.

Some languages, like Kotlin, Go, and Python, are more concise and require less code to express what we want. Java, while not the most verbose, still demands a bit more effort. Starting with Java 10, the var keyword was introduced to simplify syntax and make code more concise and readable. Using var, you can omit the explicit type declaration when extracting a variable, but the type is still inferred at declaration time and remains fixed throughout execution. However, this can sometimes compromise clarity when reading the code.

// Hypothetical scenario of a complex object declaration in Java (something I have seen often)
Map<String, List<Pair<String, Object>>> variable = o.getInfo();

// The **var** keyword introduced in Java 10
var variable = o.getInfo();

Adopting var reduces verbosity, but it comes with a cost: clarity. One way to mitigate that impact is to give variables meaningful names, so that even without an explicit type declaration, the developer can infer it easily. That said, this will not always be 100% clear.

For example, if you are dealing with a method called #getDate, you can guess the return is a date, but you cannot be sure whether it is java.util.Date, java.time.LocalDate, java.time.LocalDateTime, or even a String or long representing a timestamp. To know for certain, you need to take an extra step: open the method and check the return signature. Some modern IDEs already surface this information, but doing so requires interrupting your reading flow, moving the cursor, and taking an action. That creates a mental context switch.

So should you stop using var? That is not the point. The point is to show the impact of that decision. Personally, I use var often. It saves writing time and reduces the effort to read code. To mitigate the lack of clarity that can come with it, I establish global type conventions across the project and always aim to give variables good names.

In my personal projects, for instance, I prefer ULID (Universally Unique Lexicographically Sortable Identifier) as the standard for entity IDs. ULID is lexicographically sortable, which simplifies searching records in chronological order, and can be generated in a distributed manner without collision risk. Its compact format and ease of manipulation in databases make it a good fit for systems that require scalability. Depending on the project, a timestamp might be standardized as a LocalDateTime or a long. What matters is that once a standard is defined, it carries through the entire project.

If a variable needs to go through multiple transformations, I prefer creating a new type for it, encapsulating it in an object. This avoids the primitive obsession problem, a common challenge in design. The power of using objects and properly encapsulating data is often underestimated.

The same reasoning applies to variable names. They should be meaningful and clearly express the variable's purpose, making code easier to read and maintain over time. Self-explanatory names eliminate ambiguity and make code more intuitive for both the author and the reviewer. This also reduces the risk of errors, because the data flow becomes easier to follow.

The variable name should reflect both the data type and the role the variable plays in the system, especially when it needs to be exposed in an interface (API). When applying Domain-Driven Design (DDD) concepts, it is also important to adopt a ubiquitous language, meaning the code uses the same terminology the business expert uses. A good name eliminates the need to look elsewhere for information about the variable, how it should be populated, or how it can be used.

Due to lack of creativity, I have seen technical variables appear in many projects with names that have no relation to the business domain. It is worth distinguishing between what is "business" and what I mean by "technical." In a banking context, for example, "transaction" is a technical term used by the business to refer to a unit of work involving the exchange of value between two or more parties. Authentication and authorization also have business-specific technical terms: issuer, expiration, token, and so on. These may seem technical, but they belong to the business domain, not to our engineering vocabulary.

When I mention the appearance of technical variables, I am referring to variable names that have no representational value for the business. They exist only to solve a technical problem caused by inadequate modeling.

How inadequate names affect maintenance and evolution

In software development, productivity tends to decrease over time due to several factors. As a system grows, complexity increases with the addition of new features, which can lead to code erosion. This happens because of technical debt, poor design, or the perpetuation of an inadequate model. Renaming a variable, for example, can become difficult when it is exposed in an API consumed by other systems. An inadequate name creates problems not only for whoever maintains the system, but also for those who need to use it and understand how to populate it correctly.

Wirth's Law states that "software is getting slower more rapidly than hardware becomes faster." It suggests that despite advances in hardware, software tends to become more complex and less efficient over time, contributing to a decline in productivity. The Lehman Laws, based on studies of software evolution and maintenance, describe how technical complexity, business complexity, and legacy complexity interact. This is a topic worth exploring in more depth in future articles.

Continuous maintenance and refactoring: strategies to prevent code degradation

One effective way to fight this degradation over time is to adopt continuous maintenance and refactoring practices. Refactoring regularly helps reduce complexity, improves readability, and makes it easier to introduce new features without letting the codebase become unmanageable. Good design and clear, meaningful naming are essential in this process, as they make code easier to understand and modify even after a long period.

Preventive refactoring, meaning correcting design and structural problems before they become bottlenecks, can prevent the accumulation of technical debt. This is key to keeping the software agile and adaptable to changing business needs. Failing to refactor or neglecting continuous maintenance can lead to systems that, though technically functional, become hard to maintain, update, and integrate with new systems.

A simple example: imagine you change the rule inside a method and the method name no longer represents what it currently does. Refactoring the method name is a simple but necessary action to keep the code consistent, readable, and easy to understand. Otherwise, someone in the future may try to reuse that method, assuming it still performs the expected function, only to discover later that the result is inconsistent because the method no longer does what they expected.

Another important factor is ensuring that, when making changes, the Single Responsibility Principle (SRP) from S.O.L.I.D. is not violated. Adding more rules can break that principle and create the need for indirection, one of the G.R.A.S.P. principles. This can be addressed using patterns like the Specification Pattern, described by Martin Fowler in Patterns of Enterprise Application Architecture, or by applying other GoF patterns.

Practices like removing unused variables from method signatures are part of staying attentive to refactoring needs during the evolution and correction of the system, and they are essential habits for any good software engineer.

Avoid common naming errors

Naming errors can occur in various scenarios, usually due to lack of knowledge, immaturity, or poor design, often driven by time pressure. I prefer to believe that experienced professionals do not make these errors out of laziness or carelessness. Everyone, as professionals, does their best with the knowledge and conditions available to them.

"Do your best with the conditions you have, while you work toward better conditions to do even better!" — Mario Sergio Cortella

As should be clear by now, a simple, seemingly harmless action like giving a variable any name can directly impact the readability and maintainability of the code. That in turn creates a negative impact on the performance of whoever is maintaining and evolving the system, making it harder to understand, modify, or correct the code in the future.

Beyond that, poor design can lead to the creation of unnecessary variables, which not only hinders understanding of the system but also compromises software performance. Excessive memory consumption, increased complexity, and the overhead of unnecessary data are direct consequences of inefficient design. The result is a system that is slower, harder to scale, and more challenging to maintain.

Conclusion

The choice of variable names goes far beyond readability. It directly impacts code design, maintainability, and the performance of the team responsible for maintaining and evolving the system. Clear, precise, and meaningful names help shape the code's structure, making it more cohesive and intuitive, which simplifies understanding, modification, and long-term evolution.

While proper naming is important, it is not a solution in isolation. Continuous maintenance, regular refactoring, and the application of good design practices are all necessary to prevent system degradation and ensure sustainable evolution. A codebase built on good naming and design practices makes the team's work easier, keeping the system agile, scalable, and adaptable to change.

Careful naming of variables is therefore a practice that protects code quality, improves team productivity, and keeps the software healthy and efficient over the long term.

In the next article, we will explore scenarios to avoid when naming variables, to ensure the code stays clean, concise, and easy to maintain.