Good Practices for Clean Code

introduction

In the world of software development, there is a debate about clean code practices that seems to have no end. This article does not seek to close that discussion, but rather to enrich it from the perspective of a developer who, after listening to several mentors, reads essential classics such as Clean Code by Robert C. Martin, and facing the everyday challenges of the trade, would like to share some key points about conscious code writing. The experience of maintaining legacy code, collaborating with teams under established agreements and conventions, and seeking to continuously improve clean code practices, has led me to reflect on the importance of each word in the code and its impact on future interpretation by other developers, and even by yourself, during the development process or when carrying out maintenance or updates weeks or months after writing it.

I dedicate this article to my Kranio team, with whom every day we write together the work of our collaboration: a code that reflects our individual and collective learning, and that in the future will surely be bequeathed to new developers who must interpret our intention for functional clarity and excellence, aspects on which we focus our efforts.

If you have noticed the recurrent use of the word “work” up to this point, know that this is no accident. Many of us would agree that, as developers, we are the “authors” of every line and block of code written. From this perspective, we can approach writing code as what it is in a literary sense: the telling of a story, one that has a dilemma to solve, main and secondary characters, events and a fallback that leaves everything in its place or alternative endings if necessary.

As in any language, the code is full of adjectives, nouns and verbs, and it's crucial to use them consciously so that our narrative makes sense and is understood on its own, without the need for excessively elaborate documentation. If well written, the code will be clear and evident, forming paragraphs that are naturally integrated into a perfectly readable composition.

Naming variables

Giving a name to a variable in everyday code writing may seem like a trivial thing, but reality couldn't be further from this assumption, this task is actually something that more than deserves our full attention and even reflection before naming the different characters in our narrative. How can something as simple as naming our variables be so important? And yet, if we put ourselves in the shoes of our readers, it is crucial to have an adequate description of each element (primary or secondary character in the story told) to correctly interpret our intention.

In the following example code there will be multiple points of reflection, but let's first try to interpret the purpose of this function after a quick read.




Although it is a fairly simple code, it forces us to infer its objective guided by the name of the function and the processes it performs, being obfuscated by its enigmatic variables, among other bad practices.

Now let's look at it again.




It is still not a particularly pleasant function, however, its intention is much clearer, it is easy to recognize the protagonist of the described process and its purpose, which despite having side effects, is evident.

Here are some of the points that I consider most relevant when naming our variables:

Avoid misinformation

Programmers should avoid confusing or misleading variable names that make it difficult to understand the code. It is important to use terms that accurately reflect their purpose, avoiding ambiguous abbreviations or names that could lead to incorrect interpretations. In addition, names with minimal variations can be confusing and should be clear and distinctive. Misleading names, such as “l” and “O” that resemble “1" and “0", should be avoided to improve readability and avoid errors.

Avoid acronyms, use pronounceable words

The names of variables in programming must be pronounceable, since spoken language is a fundamental part of our cognitive capacity and programming is a social activity. Using unpronounceable names, such as “GpstBusr”, can lead to absurd situations and hinder effective communication between developers. Clear, pronounceable names allow for smoother conversations and avoid misunderstandings when explaining the code to others. As far as nomenclature is concerned, “shorter” is not always “better”, on the contrary, “clearer” is.

Use business domain names or solution domain names

To improve the clarity and comprehension of the code, it is crucial to use a consistent word for each abstract concept and to avoid word games that can create confusion. The names of functions and variables must be consistent and reflect their purpose, avoiding ambiguous terms or without clear context. Using recognized technical names makes it easier for other programmers to understand, while in the absence of technical terms, names from the problem domain should be used. Adding context to names, through classes or prefixes, also helps clarify their function in the code.

Functions

If, like me, they started their software development career in the modern times of object-oriented programming and structured programming, then the terms “routines” and “subroutines” are something that we only know from “classic” literature, so we understand that the acceptable sizes for a function have varied from several hundred lines to modern minimalism that seeks to simplify everyone's life with concise and clear functions.

Functions should be brief, focused on a single task, and maintain a consistent level of abstraction. To achieve this, it is recommended that blocks of code in conditional or loop structures contain only the invocation of a function with a descriptive name, thus reducing complexity and improving readability. A function that does “just one thing” breaks down a larger concept into steps at the same level of abstraction. The mixture of levels of abstraction in a function or the division of it into sections indicates that it is performing more than one task, making it difficult to understand.

In the words of Robert C. Martin: “FUNCTIONS SHOULD ONLY DO ONE THING. THEY MUST DO IT WELL AND THAT MUST BE ALL THEY DO.”

Let's look again at the function to get a user's posts:




Can you notice the “side effect” of it? The function claims to obtain a user's posts, but along the way it filters by period of time. According to the premise of “sole responsibility” this block must be separated into its own function. Like this:




Avoid duplication

“Innovations in software development have been an ongoing attempt to eliminate duplication from our source code.” Code duplication, such as the repetition of an algorithm in multiple places, increases code size and the risk of errors, in addition to complicating future modifications. This problem can be mitigated through techniques such as creating reusable methods, improving readability and reducing risks.

Nomenclature

Consistency must be sought in verbs and nouns from modules to variables. Using synonyms to name functions in the same sequence is like changing the names of our characters in the middle of a story.

Choosing descriptive and precise names for functions and methods is crucial to improving code clarity and design. A well-chosen name reflects the purpose of the function, making it easier to understand and maintain. There is no need to fear long names if they are more descriptive, as they help to better narrate the logic of the code. In addition, using a consistent naming convention throughout the code helps to tell a clear and consistent story. Experimenting with different names to find the right one can lead to improvements in the structure of the code.

Functions are the verbs of the language and the classes are the nouns.

Parameters

The ideal number of arguments for a function is zero, and you should avoid exceeding two, since more arguments complicate understanding, testing, and using the code. Output arguments and indicators (such as Booleans) are especially problematic. In functions with multiple arguments, it's often better to group them into objects for simplicity. In addition, the use of clear and consistent names for parameters is key to making the code easier to read and maintain. When lists of arguments are needed, they should be treated as a single argument, following the same rules.

Take our function to filter posts by time range as an example. If we analyze the parameters, we see that two of them are so highly related that they are actually part of the same concept and therefore their grouping by class would allow us to read it faster. In this way:




Order

We want our functions to be able to be read from top to bottom, like paragraphs of our narrative, the content of each use is understood by the context of the previous one and so on until we reach the descending.

Comments

Like many of the discussion topics on clean code, the comments are highly controversial. We see a lot of articles talking about how to write “good” or “meaningful” comments, however, I think we can all agree that the best comment is the one that didn't need to be written.

Recapitulating the narrative that the code tells about the concept, we can reconsider that a good code is self-explanatory. In the same sense, every time we feel the need to write a comment, let's first analyze if in reality what we should do is to refactor the code in a way in which this comment is not necessary because of the clarity of the code per se, this investment of priorities will always yield better results.

Usefulness

Well-placed comments can be useful, but dogmatic and old comments can be harmful. While in an ideal world, programming languages would be so expressive that we wouldn't need comments, the reality is that comments are often used to make up for our inability to fully express ourselves through code. Despite this, comments should not be celebrated as an ideal solution, but rather seen as a necessary evil.

Tendency to misinform

Comments can be misleading and out of date. As the code changes and evolves, comments tend to lag behind and may become incorrect. A common example is a comment that no longer matches the code it refers to, especially when new variables or other changes are added to the code. Inaccurate comments can cause confusion and lead to incorrect expectations, so it's best to minimize their use and focus on writing clear, expressive code.

Comments versus clear code

A common reason for creating comments is to explain disorganized or incorrect code. However, it's more effective to fix the problem rather than relying on comments to clarify confusing code. Clear, well-structured code is preferable to complex code full of explanatory comments.

Well, as we come to the end of this reflection, there are still many issues to be addressed in relation to important practices to be taken into account when writing clean code! Until the next edition!

Khristian Rojas

September 17, 2024

Entradas anteriores