Clean Code: The Beginning

Based on the date of publication, this article is the completion, and in fact the preface, to a series of articles written on the topic of clean code. The articles reflected his view on creating flexible and readable program code, where the emphasis is on examples.

The current article is devoted to the general principles of writing code that is easy to read, understand and maintain (KISS if you prefer). I would like to warn you right away that many of the provisions reflected in this article may raise doubts about their correctness, but this is the author’s personal opinion, based on experience.

Clean code is not for everyone. Whether the reader admits it or not, the developer community is not homogeneous. We cannot be compared even at the beginning of our careers. There is a big difference between a graduate of a month-long course in Python and a university graduate with theory and knowledge of several programming languages. And no one has canceled the official division.

  Developer survey result

Developer survey result

The image shows a selection from the results of the StackOverflow developer survey for 2023 (2023 Developer Survey).

Understanding the concept of clean code comes with experience. It is understanding, not knowledge. You can memorize and repeat all the principles at an interview, but not understand how to apply them.

Clean code is the preserve of experienced developers (Developer Experience). It is these specialists who organize work on the project in such a way that the rules for constructing clean code are carried out as if by themselves and are accessible for execution even by beginners.

Clean code without prior design is useless. Design allows you to organize and break down the entire software product into modules even before the coding process begins. A poorly organized, read designed project always leads to financial losses, and it is for financial gain that we all work.

At this point people often start arguing with me, citing many project management methods: Waterfall, Agile, Critical Path Method, Critical Chain Project Management, Scrum, Scrumban, PRINCE2, Extreme Programming. These are only those with which I am familiar, as they say, “I was able to touch them with my hands.”

But I will express a very seditious thought here; not one of them solves problems in the implementation of projects. With experience, I increasingly come to the conclusion that faith in one magical method of control often develops into Faith.

Well, now an explanation of how the project management method is related to design and what does clean code have to do with it.

I hope I'm just unlucky. I have never had to participate in, and have never heard of, projects in which an economist would participate from beginning to end. This graph is a summary view of recent cost estimates, with the X axis representing time as a percentage and the Y axis representing the percentage of budgeted costs.

Project cost schedule

Project cost schedule

It is clear from the graph that the ideal project is a straight line. All work was 100% completed and everything was within budget.

In reality, not a single plan is followed one hundred percent, and all improvements beyond those agreed upon are carried out at the expense of the developers. One can object here too. Point out how to correctly draw up a contract, or calculate the amount of a project, but that’s not the point. Developers can influence the “sag” of this schedule only by organizing the work. Distribution of all tasks evenly throughout the entire length of the project.

At this moment it is customary to talk about Agile, Scrum, etc., but in reality everything depends on proper design. The entire functionality of the project must be correctly, I emphasize, correctly divided into components. Even before writing the first line of code, it is necessary to understand what parts it consists of and the number of these parts must comply with the KISS principle, that is, each module must be easy to understand.

The most inexperienced team member should have an idea of ​​how to implement a particular module. Everything is according to the classics, we focus on the weak link.

And at the first stage of the project (points on the graph), strict management techniques (for example Waterfall) show themselves well. It is advisable to comply with it even at the stage of coordinating the project with the customer. But it depends.

The second stage, creating a Minimum Viable Product (MVP), here it is better to loosen strict control and manage more flexibly. It usually helps if the modules are developed sequentially, forming a workable framework (for example Critical Path Method), amenable to comprehensive and load testing. By identifying problems at an early stage.

By the last two stages, architectural errors should have already been identified and it becomes important whether the design team divided the project into modules correctly. An experienced team, before starting development, can draw up a list of tasks that is close to the real one, which can be managed through a flexible approach (for example, Agile).

In an ideal world, the design team can not only make the graph a straight line, but also bend it, that is, complete the project without strain.

It's time to explain how project management relates to clean code. Clean code and compliance with the KISS principle (in my opinion, these are the same thing) are largely a consequence of the correct organization of work on the project. A well-designed and managed project results in predictable changes. There is no need to rewrite entire blocks of code on the go, since the entire project is divided into small modules that interact with each other through SOLID, Design Pattern, etc.

Clean code uses design patterns in a natural way. Actually, this statement does not require additional clarification, but it is worth clarifying. In Russian, the borrowed term “design patterns” is perceived as a single semantic construction. Like, for example, the concept of “window” is literally an opening in a wall, but we mean a structure made of glass and frame.

So it is with the term “design patterns”. Design patterns translates as design patterns, that is, patterns that are used in design. Therefore, it is necessary to use them in design.

A developer without sufficient programming experience cannot carry out the design correctly and, consequently, cannot apply templates. This should not be required of ordinary programmers; the design team should be responsible for this. As you can see, according to a survey of developers from StackOverflow, they get twice as much.

Once we understand the terms, I’ll give an example of a task from a design group from a real project. This is roughly how it ended up on the Task board.

/* Модуль хеширования пароля.
* Алгоритм хеширования - ГОСТ 34.11-2018
* Соль - "qwery"
* Количество циклов хеш + соль — 5
* Параметры: pass – сторока символов
* Возврат — хэш в виде строки
*/

namespace ModulePasswordHash {
	class IPasswordHash
	{
	public:
		virtual std::string get(std::string pass) = 0;
	};
}

From the obvious, the Facade pattern is used here to separate the module from the business logic. Dependency inversion was performed for the same purposes. Comments after the implementation of the block will go into documentation through the documentation system.

Of the not obvious, the programmer does not need to know anything about how this module will be used, its task is simple and clear. Less ambiguity, fewer errors. In case there is a need for changes, the problem is easily localized, and even if changes are difficult to make. The entire module can be easily replaced.

Testing such functionality is simple and effective, but more on that later.

Clean code requires no testing effort. Testing as quality assurance (QA), formulated as a separate discipline relatively recently, has therefore become overgrown with myths and contradictory statements.

The first of these unsubstantiated statements assumes that the tests should be written by the person who writes the main code. This contradicts all the experience of technological development and common sense. What if the quality of construction is confirmed by the builders themselves. What if the freshness of the food is confirmed by the employees of the food outlets themselves.

Therefore, writing tests and main functionality should be divided between programmers. At the moment, there is no clear practice as to whether specialists should be separated by specialization or whether tasks should simply be distributed within one team. Both approaches have their advantages and disadvantages.

The second statement, which does not stand up to the test of practice, says that it is necessary to cover one hundred percent of the code with tests. Excessive test coverage hinders subsequent software development. Practice shows that when modifying or correcting erroneous solutions, it is often necessary to perform structural transformations in the code, which entails reworking the test base.

Each such change is caused by the need for structural restructuring of entire modules or even a group of them. At the same time, tests covering individual classes and methods do not help speed up the solution of the task. On the contrary, the programmer first decides whether changes can be made without breaking the structure of the test base, then tries to make a minimum of changes to the test base, and if this fails, he is forced to rewrite significant parts of the code and test base.

All these actions not only take a lot of productive time, but also do not have clear evaluation criteria. How to determine where the boundary is beyond which further modifications are not effective and the tests need to be rewritten.

Another problem that requires increased attention is dangling tests. Tests, usually at a higher level, cover the operation of several classes, methods or their relationships. Such tests either deliberately remain partially or completely non-functional, or stop performing their functions without causing error messages, which is even worse.

This is where the economy comes into play. As a rule, at the final stages of development, customer requirements are adjusted and cyclical mechanisms (Agile) are turned on in full force. The same thing happens when updates are released. In both cases, the amount allocated for the project has already been practically spent. Increasing the deadlines, and therefore the budget, must have a strong justification. Which does not include restoring the integrity of the test base.

It turns out that the restoration of the test database is postponed, and then simply not carried out. The further from the moment of the first tests, the more expensive their support becomes. The author has observed more than once how the development team put forward well-founded reasons for postponing the rework of the test base, and management agreed with them. Knowingly worsening the stability of the system. Fulfillment of customer requirements is a higher priority.

Well, the obvious solution to this problem is to initially reduce the test coverage area. The main unit of development is the module, and in test development as well. The minimum unit of testing is the “façade” of the module. Within this functionality, the programmer has the right to decide to write additional tests independently.

Clean code, simple Test-Driven Development TDD. This statement is based on a simple idea: if before starting development you can get a detailed description of the module interface, then why not write a test that checks its operation before writing the module itself.

Subsequently, clear criteria appear. Unit tests will check that the module works correctly. Above them are integral tests that check the functionality of the modules in combination. All these tests are the responsibility of the tester and their maintenance becomes natural.

During the next development cycle, the design team creates new requirements. A testing specialist checks the relevance of the tests, issues tasks for correcting the tests, and later the main code on the Task board. In this case, the programmer is completely free to make decisions. He has the right to completely remove the functionality of the module and write it again, or change only a small part of it.

Clean code solves performance problems. The main argument against clean code practices is performance. The use of inheritance, virtual functions, small classes, many modules, etc. slows down the logic. This statement is true, but practice shows that most performance problems arise from human factors. Errors in the construction of business logic, an incorrectly chosen framework, and incorrectly used algorithms affect performance degradation much more than virtual tables. The modular design of the application, the ability to quickly modify and the ease of constructing integral and load tests significantly reduce the number of errors affecting performance.

Clean code does not fit well with ready-made frameworks. As with patterns, this is not a problem with the frameworks themselves, but with their incorrect use. A framework (eng. framework – “framework, structure”) is simply a tool that allows you to solve typical problems and must be used precisely as a tool.

Unfortunately, quite often the framework becomes a tool, some kind of fetish, almost a religious trend. We have to deal with situations when developers first choose a framework, and then begin to delve into the business logic of the application.

It is also bad practice to use frameworks for purposes other than their intended purpose. This is when a multifunctional framework is taken and less than 50% of the functionality is used. It is better to use several small and not so fashionable ones than to embrace everything in rhinestones.

The next subtle point is planning the update of the framework. During the design stages, you need to immediately decide whether the framework of your application will be updated following the release of new versions of it. If not, then this must be indicated in the documentation, explained to the customer, and then you can directly use its functionality. Tightly integrating with the written code.

If you plan to implement updates, it is better not to rely on backward compatibility, but to use the framework through structural patterns. In the future, this will greatly simplify the work on new versions of the application.

Clean code eliminates refactoring. Refactoring (eng. refactoring), or code redesign, code processing, equivalent transformation of algorithms is the process of changing the internal structure of a program, without affecting its external behavior and aimed at facilitating the understanding of its work.

This is a formal definition of the term, and the key here is a change that does not affect external behavior; therefore, it makes no sense to discuss refactoring in the context of releasing new versions. It all comes down to proper design again.

The modular structure, the presence of modular, integration, and especially load tests, will allow the majority of design flaws to be identified and corrected at the early stages of the project. At the end of the project, when there are the most adjustments from the customer, it will be possible to reduce the need for refactoring to a minimum. By adjusting the operation of individual modules or completely replacing them.

Conclusion. Unfortunately, we do not live in an ideal world, and every programmer cannot write clean code on his own initiative. Simple, clear, reliable and easily scalable code is written by teams.

Therefore, the solution to the problem of clean code is seen in the proper organization of the development team. Experienced programmers with the appropriate skills formulate the project, its simplicity, clarity and reliability. Those without such experience are engaged in concrete implementation and learn, including from the mistakes of more experienced ones.

PS Initially, I planned to give examples from practice to confirm my conclusions, but later I considered this to be incorrect in relation to those who made them. We all make mistakes.

However, analyzing errors allows you to learn from them, therefore, those who consider it possible to publish cases from practice, please comment or send them by email.

P.P.S. To compensate for the lack of examples, here is a list of books on the topics raised in the article.

Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin, August 1, 2008, ISBN-978-0132350884

“Clean architecture. The Art of Software Development” by Robert Martin (Clean Architecture: A Craftsman's Guide to Software Structure and Design by Robert C. Martin, September 10, 2017, ISBN-978-0134494166)

Effective Software Testing by Maurício Aniche, 2022, ISBN 978-1-6334-3993-1

“Refactoring. Improving the Design of Existing Code by Martin Fowler with input from Kent Beck [и др.] (Refactoring: Improving the Design of Existing Code, Martin Fowler, Paul Becker, Kent Beck, John Brant, William Opdyke (Autoren), 1999, ISBN 978-0-201-48567-7)

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *