Best practices in clean code writing. Advice from an experienced software agency

Best practices in clean code writing. Advice from an experienced software agency

The term “clean code” refers to code that is readable, easy to understand, and well-organized. Focusing on code quality provides countless benefits, including better future maintenance and the elimination of many potential problems. The ability to write concise code is something that distinguishes professional programmers in the job market. We are aware that following clean code principles can be more time-consuming and requires attention to detail. However, it can provide advantages in the long run.

This paradox arises from the phenomenon known as “technical debt.” It can affect every project, not just software development. If you work in a workshop and don’t put tools back in place, you will save time. Cleaning up the mess will take longer though. Similarly, writing chaotic code is faster. However, soon enough programming tasks can get more complicated and will require more time to be finished. Without further ado, let’s take a closer look at all the best practices that can lead to cleaner code.

The rules of writing clean code

Below, you will find guidelines for preparing code that is easy to read and work with. This knowledge is based on years of our experience. We know how important clean code can be for further product development, quick bug fixing, and future redesigns. Our team also likes to work with code that is straightforward and comprehensible. With these tips, you will avoid many mistakes and make each line of code much better.

Naming classes, methods, and files

This is the most basic and essential practice that is worth knowing before you even start the production process. Each name should be descriptive enough to tell from first sight what a particular function or variable holds. Code analysis shouldn’t be necessary to determine that. Using abbreviations might be tempting, but in reality, they are never unambiguous and can be interpreted in various ways.

Here’s an example of this rule:

Bad naming

  • value1, value2, value3, etc.

Good naming

  • userName, productID, birthDate, etc.

Comments

This rule can be controversial and it often divides developers. Comments in code are often perceived as something positive. Their purpose is usually to describe how a few lines of code in a larger function work. They can also explain a business logic that led to the use of a specific approach.

However, problems can arise when the code to which the comment refers changes. Usually, when encountering someone else’s comment in the code, programmers think: “I didn’t write this, so I’ll leave it as it is.” This is a symptom of a broader phenomenon that often occurs in programming. The famous: “I don’t want to touch it because I might break it.” You can probably predict the result of such actions. The code is changed, but the comment doesn’t reflect these changes. Now it’s not only incomprehensible but also outdated or even misleading.

Look at the whole process from the perspective. Why should I write a comment? Because something is hard to understand. In that case, you should rather focus on writing code that will be understandable on its own, rather than adding a comment to it.

The comment above contains only fragmentary information and will quickly become misleading if the code is modified. What we would recommend is to rewrite this part to make the sequence and specificity of actions clear at first glance.

Here’s what it can look like:

Instead of calling a string of consecutive, less meaningful functions, it’s better to transfer this responsibility to another class that will contain many small functions with descriptive names. This way, in case there’s a need to add another rule – for example, changing the encoding to correctly process Polish characters – we do not have to decipher and modify complex lines. Instead, we can just create another tiny function and call it at the end of the string.

Logical expressions

We often come across logical expressions that are represented in code by the following operations:

  • “or” (or “||”)
  • “and” (or “&&”)
  • “not” (or negation – “!”)

Unfortunately, they are often difficult to understand. It’s easy to make a mistake when interpreting them. Example: what does the line !$number->isNegative() mean? A number that is not negative – so a positive number? Yes, but remember that it can also be a zero, which is not so obvious. Such an expression is better written in a longer form as $number->isPositive() || ($number === 0). It translates to “a positive number or equal to zero.” Pay attention to the round brackets, which indicate the order of operations. The compiler or interpreter may not require them, but they increase readability.

Another case is complex logical expressions that consist of more than two operations:

Here, the large number of dependencies is hard to comprehend. There are so many brackets that indicate the order in which the individual parts of the entire expression should be considered. It’s incredibly confusing. 

Even when we break the expression into more accessible language, it will still be problematic to understand. “The user can post content on the blog OR is an administrator, OR the blog allows everyone to post content AND is not moderated.” Doesn’t sound right, don’t you think?

The solution to the problem is very simple to implement. We just have to close consecutive pairs of expressions that occur next to each other in one variable or method. This way we get a much clearer logic. It would go like this: “The blog is open for all posts OR the user can post on the blog.” Here’s how to achieve that:

Building abstraction layers

This rule is quite simple to understand, but its implementation can be challenging. It usually consists of various techniques, but two main principles:

1. Hide implementation details

The first goal is achieved by encapsulating all small operations into their own small classes and methods. Especially those that appear repeatedly throughout the code. Then, private methods within the classes should be placed at the end and named descriptively enough to help the next programmers to decode each function without a need to analyze them line by line. Seeing a call to the “setInitialReportDate” function, the dev will know what it does. They won’t have to determine how it was coded exactly.

2. Do not mix abstraction layers

The second step is also simple to achieve, although it might not be clear from the beginning. To explain it in a few words: In the classes that are ultimately used to perform a given task, call only the methods of the following classes. That’s because, in these places, direct operations on dates, amounts, databases, etc. shouldn’t be included. Instead, the programmer can call methods that will do these.

Simply put, instead of building a particular functionality from many small blocks scattered in the code (which results in chaos), small elements can be closed in their own, concise classes and methods that can be reused in any place without creating clutter.

Here’s a simple example that illustrates both of the above-mentioned rules. Let’s start with the code that doesn’t follow them:

The snippet above aims to build a financial report for a specific quarter. Each required operation is performed one after the other. The code related to data manipulation is mixed with parts responsible for retrieving data from the database and data input validation. Thus, if there’s a need to prepare a similar report, but with different variables (for example, for the whole year) and in a different format, many lines of code will have to be analyzed. Using the tips from this section, we can significantly simplify this process. All that has to be done is to move all small operations into smaller classes and functions. Later, we will just have to call them in the right order and with appropriate arguments.

Here’s what it can look like:

Now, the operations on data, amounts, and currencies, as well as data validation and other details are hidden in the constructor and within the class methods that are building the report. They are executed in sequence. We’ve created new classes, such as Quarter, which can validate if the specific three months were indeed provided. Another useful addition is ReportFormatterInterface which hides formatting details.

Not mixing abstraction layers is an important practice. For instance, if we want to add the report author to the example code above, we wouldn’t do it by adding more lines to it. This feature should be implemented in a separate small method that will be called in the chain. We could even hide it in the constructor class that builds the report. This way, we won’t clutter the code with unreadable lines that would have to be read and understood each time the code is worked on.

Following our tips when building successive abstraction layers saves developers a lot of time and hassle. The code we provided showcases it quite clearly. If another type of report is needed, we don’t have to look through hundreds of code lines, but less than ten. If there’s an error, we can fix it in one place by rewriting only a small part of the whole software project. Results? Improved productivity, better overall quality of the final product, and fewer complications during the production phase and post-release maintenance.

The benefits of clean code

The approach we’re discussing in this article provides numerous advantages to all parties involved in the software development process. It’s positive not only for the developer that writes the code and their teammates. Being mindful of code quality, its readable structure, and each line’s purpose is the first step towards delivering software that really stands out and meets the requirements. No one wants a product with complicated code that is hard to update and repair in case of emergency. We also can tell, based on experience, that writing concise, clean code can speed up the development process. This argument is often crucial for product owners.

Clean code leads to lower risks of errors, bugs, and technical malfunctions. Thanks to its reduced complexity, detecting potential problems becomes much easier. All layers of the product are condensed into manageable parts that are understandable for everyone on the development team. Thanks to that, collaboration becomes seamless. It’s especially crucial for teams that contain devs from the client’s company and outsourced specialists. Even if the project scope evolves and changes, editing the software’s code is a much more streamlined process.

There are many techniques and patterns that aim to make code more readable. One rule we would recommend to every programmer is straightforward: write code for people. For other developers, the brand you work for, and their end customers. Every piece of software that is built according to the guidelines we’ve provided will outrun products that were created without them in mind. That’s the first step to hooking consumers, winning with competitors, and becoming the top business in your niche.

Conclusion

We hope that this short but practical guide will be useful for developers and not only. Sharing our expertise might make the IT industry a better place. It’s also a great opportunity for entrepreneurs to check out the production process from the inside. They can see how we manage challenges that occur while building solutions for their companies. We don’t want to hide anything. We always encourage brands to collaborate with professionals that are transparent about their work.

Do you want to outsource your software development needs to specialists that put clean code on top of their priorities? We should talk. G-Group.dev puts a lot of effort into hiring and training top talent. We provide developers that know all the best practices, have experience in various technologies, and pay attention to details. You will be satisfied with our cooperation and gain a solution that will level up your business. Let’s chat!

G–et
a quote

It is important to us that we understand exactly what you need. Complete the form and we’ll get back to you to schedule a free estimation call.

Message sent successfully