Capa do artigo: Minimalist Code: Concision in Naming, Precision in Code
Series Minimalist Code · Part 3 of 3

Minimalist Code: Concision in Naming, Precision in Code

Series Minimalist Code Part 3 of 3

In the previous post, I covered concerns about specific details when naming variables. Now let us bring in some points of debate and offer some direction on naming concision.


Diverging from Clean Code

Unlike what Clean Code by Uncle Bob proposes, which recommends using prefixes like is or has for boolean variables, as in isEffective instead of simply effective:

private boolean isEffective = false;

if (isEffective) {
    // ...
}

According to the author, these prefixes make it more evident that we are dealing with a boolean variable, avoiding confusion with objects or other types and clearly indicating a true or false value.


My view: clarity through concision

I think about it differently. My focus is on writing less and reducing cognitive effort. If I am declaring a boolean variable, it is because I intend to evaluate it in a control structure, such as an if. And inside conditional expressions, by definition, we only deal with booleans.

For that reason, I prefer something more direct:

private boolean effective = true;

if (effective) {
    //...
}

The variable usage in the code becomes much cleaner. I know it is a boolean because, inside a conditional expression, only boolean values are evaluated. Whether in a while or an if, we are always dealing with the results of boolean expressions.

If I write 2 > 3, the result is false. If I replace the numbers with variables, I am still dealing with a boolean expression. Simple as that. Beyond this, if the variable is declared inside a class or object, the IDE already indicates the type, as it would for any other value.

There is, however, a case where this rule does not apply: when the variable is not a direct boolean, but rather a function that returns a boolean. In languages like JavaScript, Kotlin, and Haskell, functions and lambdas can be treated as variables and even passed as parameters to other functions.

Passing a function as a parameter may sound strange at first to someone who has not encountered it before.

This brings us to some foundational concepts of functional programming. Beyond the traditional map, filter, and reduce, two particularly relevant concepts are Currying and Higher-Order Functions (HoF). Currying transforms a function with multiple parameters into a chain of functions that each receive a single argument, enabling greater flexibility in function composition. Higher-Order Functions are functions that accept other functions as arguments or return a function as a result, making code more modular and reusable. It may sound complex, but we can explore that in more depth another time.

Back to the point: when declaring a function that returns a boolean, using the is prefix makes sense, because it follows the typical method naming convention in object-oriented languages, where get, set, is, and has are standard prefixes.

In that case, the variable no longer represents a direct value, but a function call, so naming it with a prefix is more intuitive and consistent with the expected behavior.

A simple currying example:

// Curried function to check if a number is greater than an expected value
const isGreaterThan = (expected) => (value) => value > expected;

// Creates a specific function: checks if value is greater than 10
const isGreaterThanTen = isGreaterThan(10);

if (isGreaterThanTen(15)) {
    console.log("The value is greater than 10.");
} else {
    console.log("The value is 10 or less.");
}

Recall what I said about conventions. If you are following a development style based on Fluent Interface, then the use of prefixes like is, has, get can (and perhaps should) be suppressed in favor of fluent readability.

In that style, the code reads more naturally, like a sentence. For example:

order.isPaid(); // conventional
order.paid();   // fluent style

There is no right or wrong here. It is a matter of preference and consistency. But if you choose to follow the more fluid model, it is essential to discuss it with the team and formalize it as a collectively adopted standard.

These small decisions, when standardized, prevent rework, unnecessary discussions in PRs, and increase development fluidity.

And to reinforce: I say more about combining standardizations with the team in this article.


Using different names for the same thing

Remember what I said in the previous post about standards? Consistency in naming is essential within a team. One of the most common and dangerous errors is using different names to represent the same concept throughout the code. It makes everything more confusing, reduces the confidence of anyone making changes or corrections, increases the risk of introducing errors through sheer inconsistency, and directly compromises readability, maintainability, and overall system comprehension.

Imagine code where the variable representing a user is called user in one function, client in another, and accountHolder in a third. While they may technically refer to the same entity, that variation forces whoever is reading to make an extra mental effort to determine whether they are different things or just distinct names for the same concept. That kind of doubt breaks the reading flow, slows down understanding, and creates uncertainty.

This lack of naming uniformity can easily lead to duplicated logic, integration failures, and even business logic bugs. The worst part is that all of this can happen simply because developers assume, based on the name alone, that they are dealing with distinct entities, when in reality they are facing a semantic inconsistency.


The confusion of negation

One of the common challenges in writing code is the use of negation in conditional expressions. We will explore this further when discussing minimalist code in functions, but here is a well-documented fact: the human brain has difficulty processing negations. Studies examining this have existed since 1975.

Think about it: when I say "the brain does not properly understand 'not'", notice how your reasoning slows slightly and you probably re-read at least twice to follow the sentence. Negation adds an extra layer of complexity to linguistic thinking. When a sentence involves multiple negations, interpreting the intention becomes even harder. Some studies indicate that the brain first processes the sentence as affirmative and only then applies the negation. Others show that negative sentences require significantly more time to comprehend compared to positive ones. In other words, beyond the natural ambiguity of language, there are factors that amplify cognitive complexity.

Now look at this example:

boolean notFound = false;

if (!notFound) {
    System.out.println("Item found.");
} else {
    System.out.println("Item not found.");
}

At first glance, notFound seems like a clear variable. But notice what happens when you negate an already negative variable. Even in a simple example like this, reading becomes less intuitive and requires extra effort to interpret.

And here we are dealing with a single variable. Now imagine compound expressions with multiple negations chained together. The difficulty in reading and understanding grows exponentially.

This happens frequently because requirements tend to be written with negation. Something like "in exceptional cases, the value must not be A, B, or C." The developer, especially at a less mature stage of their career, tends to transcribe that logic exactly as described. If the task says "If it is A, do this. If it is B, do that. Otherwise, throw an error," they will follow the text literally, without evaluating more effective ways to structure the logic or applying design patterns to make the code more readable.

Imagine a user story like this:

"As a user, I want to ensure the system does not ignore inputs that are not invalid, so that neither incorrect nor correct data is discarded by mistake."

Writing well is a skill, and language can both enable and limit communication. We can only express ourselves within the limits of what we know of the language, which itself carries ambiguity. Having a strong vocabulary and the ability to produce clear, comprehensible text for others is therefore essential for effective communication.

"The greatest problem with communication is the illusion that it has taken place."William H. Whyte

It is worth reinforcing: immaturity has no relation to years of career. Maturity is tied to the ability to better understand problems and make good design decisions. Reflecting on the impact of negation on code readability is one of those signals.

One essential step toward making code more comprehensible is avoiding variables that indicate negation. Although it may feel natural to declare a boolean variable with a negative name, it can make the code harder to interpret. The better approach is to reframe that logic as a clear affirmation, eliminating the need to process a negation.

var off = true;

if (off) {
    System.out.println("Off");
} else {
    System.out.println("On");
}

The code works, but the reading is not intuitive. When someone analyzes this logic, they first need to interpret that off means "turned off," then confirm that the condition is true, generating unnecessary cognitive effort. Now compare with this version:

var on = true;

if (on) {
    System.out.println("On");
} else {
    System.out.println("Off");
}

Here, the interpretation flows naturally: if on is true, the device is on; if false, it is off. There is no need to mentally process an inversion.

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

Remember: the goal is not just to make the code work, but to make other developers able to understand it with minimum effort. Clear and direct code reduces the chance of errors and improves maintenance. The more intuitive it is, the less time and energy are spent interpreting its logic.


The importance of semantic names

One day I was helping a developer with a problem and, while analyzing the code, I came across something like #isStatus(). I commented right away: "Wow, that completely broke any expectation I could have about an attribute called status." Curious, right? Even following some Clean Code practices, using isStatus as a boolean variable name created a serious problem: the name suggests one thing but represents something entirely different.

When I see status in an object, my immediate expectation is that it is an enum, a string, or in the worst case, as is still common in legacy systems, an int. But there it was, simply a boolean. That breaks expected semantics, hinders understanding, and generates confusion even in simple situations.

Another classic example is a variable named data. At one point in the code it represents a birth date. At another point, it is a generic object coming from an API. That reuse compromises semantic clarity and forces the reader to constantly reestablish context to understand what that variable is about. It reduces readability and increases the cognitive effort of whoever is reading or maintaining the code.

Choosing inadequate names breaks one of the foundational premises of good programming practices: creating names that are descriptive, precise, and consistent with the variable's responsibility.

Avoiding this kind of error requires attention to the problem domain, clarity about each variable's role, and a careful eye for maintaining terminological consistency throughout the system. Static analysis tools and the code review process can help identify these cases, but in the end, the greatest responsibility remains with the good sense and discipline of the developer.


Conclusion

Clear and consistent naming is essential in software development, directly impacting the readability, maintenance, and efficiency of the team. The improper use of names, such as confusing prefixes or ambiguous terms, can hinder code comprehension and lead to errors. Following well-defined naming standards within the team creates a more organized development environment and reduces the cognitive load on programmers. Maturity in programming is not measured by years of career, but by the ability to make intelligent design decisions and apply best practices. In a collaborative environment, choosing appropriate names is fundamental to ensuring that everyone understands the system and works efficiently.