
Refactoring syndrome

It is believed that software systems, being not quite material objects, are not susceptible to aging. And if we talk about physical aging, then really, the chances that the letter “o” in the class name will suddenly shrink from old age and turn into the letter “c” are really small. But instead of aging physical, software systems are aging morally. Over time, the load of errors accumulates due to inaccuracies in the initial requirements, misunderstanding of the requirements by the customer, architectural errors or unsuccessful compromise solutions; and smaller errors, such as poorly understood code, its high connectivity, lack of unit tests and comments do their dirty deed. All this leads to the accumulation of technical debt.(which was discussed last time), because of which, when adding a new opportunity to the system, you have to pay "interest" in the form of a higher cost of implementation and lower quality of the result.
There are several ways to pay technical debt, and the simplest of them is to refactor the system or some of its parts, highlight new abstractions, write unit tests, comments, or completely rewrite some parts of the system. However, as in any other business that the glorious programmer’s soul deals with, this soul very often goes too far and instead of taking a pragmatic approach to improving some parts of the system to obtain reasonable benefits in the future, it rushes with a checker to rewrite everything in a row: what is needed, what it’s not necessary and that it’s not worth rewriting at all.
All this leads to yet another metaphor, which just describes a similar unremovable desire to rewrite old code - to refactoring syndrome.
Symptom 1. Another's code - g # $ but
As soon as more than one developer appears in a team, problems immediately begin: different people may have different opinions about the formatting of the code used by idioms, the programming language, and the approach to solving applied problems. In addition, no matter how understandable the requirements, no matter how expressive the programming language, no matter how good the code is or how well it is perfectly documented (although all these combinations never occur in one place), many thoughts and intentions of the developer still remain only in his head. And, not understanding his intentions, we very often arrogantly believe that the current decision is incorrect and the same can be done differently and much better.
And realizing that the whole world around has gone crazy, many representatives of our “race” rush to barricades and begin to rewrite or refactor someone else’s code, simply because it is someone else’s code. The classical syndrome “Not Invented Here” appears and all any code written not by one's own hands is automatically incomprehensible (because there are a lot of letters) and, accordingly, bad.
Such disrespectful attitude to someone else’s code, firstly, doesn’t paint the developer, and secondly, in most cases, does not reduce the technical debt, which should be reduced due to this refactoring. The system as a whole does not become more accompanied and understandable, it just is now closer and more understandable to one specific person. Of course, for some time the maintainability of the system improves, while the author of these changes is engaged in new features, but all the benefits will end exactly when another developer takes on this piece of code. And the process begins again.
Symptom 2. Striving for the perfect code
Striving for the perfect code is the best intention when changing existing code, although we all know where such intentions lead. Blindly following ill-conceived coding standards is not much better than no coding standards at all. The beauty of the code is not an end in itself; the code can be beautifully designed, all functions can be small in size with enough comments and even covered with unit tests. But this does not mean that this code will cope with its main task, since, banally, no one bothered to find out this task from the user.
To get the perfect result, an infinite amount of effort is required. The code should be beautiful enough, clear enough, with enough comments and unit tests. From a unit test that covers completely trivial cases, or which is so incomprehensible that its maintenance is almost impossible, more harm than good. Unit tests are an invaluable source of information about the specification of the system, each of them must tell a story about one of the ways to use this class or part of the system. They should not be many, and they should not be few; they should be exactly so that the costs of writing and accompanying them are justified (*).
Symptoms 3. “I fight because I fight”
Over time, from the height of his own new professional experience, thanks to a clearer understanding of the user's requirements and understanding of his own system, even his own code begins to look terrible. How many times have you caught yourself thinking: “Damn, who wrote this nonsense?” And then you were surprised to learn that this someone is you.
This is a normal situation, because small debt accumulate in everyone. Moreover, the reverse situation, when after a while you cannot find problems in your own code, seems more suspicious. Therefore, improving your own code is just as useful a process as improving someone else's code. But there are times when your own code undergoes significant heel changes once a year, but at the same time new features are added as slowly, and if you look at this code from the side, then nothing really changes in it.
In fact, this is some kind of previous syndrome, but when the driving force is not the perfect code, but one who understands the ideal solution. It’s normal if a person does this for his own project, when at the same time new solutions are developed for already well-known tasks, but it is not entirely reasonable when this is done for a commercial product. After all, it turns out that the code is rewritten just like that; simply because today I learned about a new feature in my favorite programming language or a new library that solves the same problem much better. But after a while all new features come out (or you will find out about them) and the process repeats again from the beginning, but no debts are paid and nothing valuable is added to the system.
Symptom 4 ... 1001
I think that every fairly experienced developer has come across all these symptoms, and probably not one dozen others. I did not set out to describe all the possible reasons that could lead to unnecessary rewriting of the code. The essence of this small note is that code, like architecture, is not an end in itself; all this is just a means to solve certain tasks of business users or any other tasks that must be solved by the application or library. The code and architecture should be good and flexible just as much as it is necessary to successfully solve these problems in a reasonable amount of time now and for reasonable labor costs to solve new problems in the future.
In almost any business, pragmatism and the absence of extremes is the best choice and refactoring the existing code is no exception. Do not forget about the Pareto law (80/20 principle): twenty percent of the effort to improve your code and keep it in good condition is often enough to improve it by 80%. And if this is not so, then perhaps the patient is more likely dead than alive and investing additional funds to cover such a large technical debt simply does not make sense and it is worth starting all from scratch?
-
(*) Just in case, I’ll say that I consider unit tests the most valuable tool in the developer's arsenal. This is an invaluable source of information about how the system should work and an amazing litmus test of design quality: if the code cannot be covered by unit tests, then something is wrong with it. This does not mean that I cover all my code with tests, but I will definitely try them on to my classes and change the design if it is impossible or very difficult to write them. But at the same time, I treat the tests with sufficient pragmatism and try to ensure that their number is the most optimal in terms of labor costs to the benefits received from them.