What is code refactoring? Basic principles and rules of refactoring

Lecture



The concept of refactoring

The concept of “refactoring” (refactoring) originated in circles related to Smalltalk, but soon found its way into the camp and adherents of other programming languages. Since refactoring is an integral part of the development of an application structure (framework development), this term immediately appears when the “structurers” begin to discuss their affairs. It occurs when they refine their class hierarchy and admire how many lines they have managed to reduce the code. Structuralists know that a good structure cannot be created right away - it should be developed as experience accumulates. They also know that they often have to read and modify the code, rather than write a new one. The support of the readability and modifiability of the code is based on refactoring, both in the particular case of structures (frameworks) and for software as a whole.
  What is code refactoring?  Basic principles and rules of refactoring

So what's the problem? Only in that known risk is associated with refactoring. It requires making changes to the working code, which can lead to the appearance of difficult-to-find errors in the program. Incorrectly refactoring, you can lose days and even weeks. Refactoring, which is carried out without formalities or occasionally, is fraught with even greater risk. You start digging through the code. Soon, new modification features are discovered, and you start digging deeper. The more you dig, the more new is opened and the more changes you make. In the end, get a pit from which you can not get out. In order not to dig a grave for oneself, one should refactor systematically. In the book “Design Patterns” it is reported that design models create target objects for refactoring. However, specifying a goal is only one part of the task; transforming the code to achieve this goal is another problem.

There are several methods of refactoring. Each method describes the motivation and technique of tried-and-true code conversion. Some types of refactoring, such as “Selecting a method” or “Moving a field”, may seem obvious, but do not be fooled by this. Understanding the technique of such refactoring methods is important for the organized implementation of refactoring. Using refactoring methods, you can gradually modify the code, making small changes each time, thereby reducing the risk associated with the development of the project. These refactoring methods and their names will quickly take place in your developer dictionary.

What is refactoring?

Refactoring is a process of changing the software system in such a way that the external behavior of the code does not change, but its internal structure improves. This is a way to systematically bring the code in order, in which the chances of new errors occurring are minimal. In essence, when refactoring a code, you improve its design after it is written.

"Improving the code after writing it" is an unusual figure of speech. In our current understanding of software development, we first create a system design, and then write code. First, a good design is created, and then encoding occurs. Over time, the code is modified, and the integrity of the system, the conformity of its structure to the initially created design gradually deteriorate. Code is slowly slipping from design to hacking.

Refactoring is the opposite practice. With it, you can take a bad project, even chaotic, and convert it into a well-designed code. Each step of this process is simple to the extreme. A field is moved from one class to another, part of the code is removed from a method and placed in a separate method, some code is moved in a hierarchy in one direction or another. However, the cumulative effect of such small changes can drastically improve the project. This is exactly the opposite of the usual gradual breakdown of the program.

When conducting refactoring, it turns out that the ratio of different stages of work changes. Design is carried out continuously during development, and not performed entirely in advance. When implementing the system, it becomes clear how to improve its project. The ongoing interaction leads to the creation of a program whose quality of the project remains high as development proceeds.

Refactoring rules

  • Having found that it is necessary to add new functionality to the program, but the program code is not structured conveniently to add this functionality, first refactor the program to simplify the necessary changes, and only then add a function.
  • Before you start refactoring, make sure you have a reliable test suite. These tests should be self-checking.
  • When applying refactoring, the program is modified in small steps. The error is not difficult to detect.
  • Anyone can write code that the computer can understand, but only good programmers write code that people can understand.

The most important lesson that this example should teach is the refactoring rhythm: testing, small changes, testing, small changes, testing, small changes. It is this rhythm that makes refactoring fast and reliable.

Principles of refactoring

Refactoring: a change in the internal structure of the software, designed to facilitate understanding of its work and simplify the modification without affecting the observed behavior.
Refactor: change the structure of the software, applying a number of refactorings, without affecting its behavior.

Refactoring does not change the visible behavior of the software. It continues to perform its former functions. No one — neither the end user nor the programmer — can say in appearance that something has changed.

Why do you need to refactor?

  • Refactoring improves software composition
  • Refactoring makes software understanding easier
  • Refactoring helps find errors
  • Refactoring allows you to write programs faster.

When should refactoring be done?

Refactoring should be done all the time. It is necessary not to decide to refactor, but to conduct it, because it is necessary to do something else, but refactoring will help in this.

  • The rule of three hits - Here is the governing council that Don Roberts gave me. By doing something for the first time, you just do it. By doing something similar the second time, you frown at having to repeat, but still repeat the same thing. By doing something similar for the third time, you start refactoring.
  • Use refactoring when adding a new function.
  • Use refactoring if you need to correct the error.
  • Use refactoring when parsing code.

Why refactoring brings results

What makes it difficult to work with programs? At the moment, four reasons come to my mind:

  1. Programs that are difficult to read are difficult to modify.
  2. Programs that have duplication in their logic are difficult to modify.
  3. Programs that need additional functions, which requires changes in running code, are difficult to modify.
  4. Programs that implement the complex logic of conditional statements are difficult to modify.

So, we need programs that are easy to read, all the logic of which is set in one and only one place, modification of which does not jeopardize existing functions and which allow expressing conditional logic in the simplest possible way.
Refactoring is a process of improving a running program, not by changing its functions, but by strengthening in it the indicated qualities that allow the development to continue at high speed.

When is refactoring not needed?

In some cases, refactoring is not needed at all. The main example is the need to rewrite the program from scratch. Sometimes the existing code is so confused that it is possible, of course, to refactor it, but it is easier to start from the beginning.

A clear sign of the need to rewrite the code - its inoperability. This is found only when testing it, when there are so many errors that it is not possible to make the code stable. Remember that before refactoring starts, the code should be executed mostly correctly.

Another time to refrain from refactoring is the closeness of the project completion date. The productivity growth achieved by refactoring will manifest itself too late - after the expiration date. Correct in this sense is the point of view of Ward Cunningham (Ward Cunningham). He compares incomplete refactoring with getting into debt. Most companies need loans for normal operations. However, interest appears along with debts, that is, the additional cost of maintenance and expansion, due to excessive code complexity. The payment of some interest can be suffered, but if the payments are too large, you will go bankrupt. It is important to manage your debts by paying back part through refactoring.

However, approaching the end of the work is the only time when you can postpone refactoring, citing a lack of time. Experience with several projects shows that refactoring leads to an increase in labor productivity. Lack of time usually signals the need for refactoring.

Refactoring and design

Refactoring plays a special role as an adjunct to design. If you think in advance about the architecture of the program, then you can avoid subsequent costly processing. Many people think that designing is most important, and programming is a mechanical process. An analogy of the project is the technical drawing, and the analogy of the code is the manufacture of the assembly. But the program is quite different from the physical mechanism. It is much more malleable and is entirely associated with deliberation. As Alistair Cockburn says:
“With ready-made designs, I think very quickly, but my thinking is full of gaps.”

There is a statement that refactoring can be an alternative to preliminary design. In such a scenario, there is no design at all. The first solution that comes to mind is embodied in the code, brought to working condition, and then it acquires the required form with the help of refactoring. Such an approach can actually act. I met people who work like this and end up with a system with a very good architecture. Those who support “extreme programming” [Beck, XP] are often portrayed as advocates for this approach.
An approach that is limited only to refactoring is applicable, but not the most effective. Even "extreme" programmers first develop some kind of future system architecture. They try different ideas with CRC cards or something like that, until they get a credible initial decision. Only after the first more or less successful "shot" proceed to encoding, and then to refactoring. The point is that the use of refactoring changes the role of preliminary design. If you do not count on refactoring, then there is a need to carry out preliminary design as best as possible. There is a feeling that any changes to the project in the future, if they are required, will prove to be too expensive. Therefore, more time and effort is invested in the preliminary design - to avoid such changes later.
With the use of refactoring, the emphasis is shifted. Preliminary design is preserved, but now it is not intended to find the only correct solution. All that is required of him is to find an acceptable solution. As the solution is implemented, with a deeper understanding of the task, it becomes clear that the best solution differs from the one that was taken initially. But there is nothing to worry about if refactoring is involved in the process, because the modification does not cost too much.
Refactoring provides a different approach to the risks of modification. Possible changes should still be tried to foresee, as well as to consider flexible solutions. But instead of implementing these flexible solutions, one should ask the question: “How difficult will it be to transform a conventional solution into a flexible one using refactoring?” If, as often happens, the answer is “very easy”, then you just need to implement the usual solution.
Refactoring allows you to create simpler projects without sacrificing flexibility, making the design process easier and less stressful. Having learned to recognize in general what is easily refactored, you even stop thinking about the flexibility of solutions. There is confidence in the possibility of applying refactoring when it is needed. The simplest solutions that can work are being created, and flexible and complex solutions for the most part are not required.

Refactoring and performance

Refactoring is usually associated with the question of its effect on program performance. In order to facilitate the understanding of the work of the program, modification is often carried out, leading to a slowdown in program execution. Refactoring undoubtedly forces the program to run more slowly, but at the same time makes it more flexible to tune the performance. The secret to creating fast programs, unless they are designed to work in hard real-time, is to first write a program that can be customized and then configured to achieve an acceptable speed.

The second approach involves constant attention. In this case, each programmer at any time does his best to maintain high program performance. This is a common and intuitively attractive approach, however, it is not so good in practice. A performance enhancement usually makes it difficult to work with the program. This slows down the creation of the program. This could be done if the result was faster software, but usually this does not happen. Speed-enhancing enhancements are scattered throughout the program, and each concerns only the narrow function performed by the program.

Productivity is related to the interesting fact that the analysis of most programs reveals that most of the time is spent with a small part of the code. If all code is optimized in equal measure, then it turns out that 90% of optimization has been done in vain, because the code that has been optimized is not too frequent. The time taken to speed up the program, and the time lost due to its incomprehensibility is all wasted.

The third approach to improving the performance of the program is based on this statistics. It assumes the creation of a program with sufficient decomposition of it into components without regard to the achieved performance up to the stage of performance optimization, which usually occurs at a rather late stage of development and at which a specific procedure of setting up the program is carried out. It all starts with the launch of the program under the profiler that controls the program and reports where time and memory are spent. Due to this, it is possible to detect that small part of the program where performance bottlenecks are located. Efforts are focused on these bottlenecks, and the same optimization is carried out, which would be applied to the approach with constant attention. But due to the fact that attention is focused on the identified bottlenecks, it is possible to achieve great results with much lower labor costs. But even in this situation, vigilance is necessary. As with refactoring, changes should be made in small portions, each time compiling, testing and running the profiler. If performance is not increased, the change is reversed. The process of finding and eliminating bottlenecks continues until performance is achieved that satisfies users.

Test development

When refactoring, an important prerequisite is the availability of reliable tests.

Test Development Rules

  • Make all tests fully automatic, so that they check their own results.
  • The test suite serves as a powerful error detector, drastically reducing the time it takes to find them.
  • Run tests more often. Run tests at every compilation - every test at least once a day.
  • When you receive the error message, start by creating a module test showing this error.
  • It is better to write and perform incomplete tests than to not complete tests.
  • Think of boundary conditions that may be improperly handled, and focus your tests on them.
  • Remember to ensure that exceptional situations are generated in case of problems.
  • The fear that testing will not reveal all the errors should not prevent writing tests that will reveal most of the errors.

Refactoring problems

  • Need to change existing code
  • The need to strictly adhere to the task
  • Cover code with verification tests

Signs that you need refactoring

  • Your software product works, but the introduction of new functionality is sometimes delayed for weeks;
  • In certain places, your code works completely differently than you expected;
  • You are often mistaken in the timing of the implementation of the task;
  • You have to make the same type of changes in different places.

Refactoring methods

  • Encapsulate Field;
  • Class selection (extract class);
  • Extract Interface;
  • Extract Local Variable;
  • Extract Method;
  • Generalization Type;
  • Embedding (inline);
  • Introduce Factory;
  • Introducing a parameter (Introduce Parameter);
  • Lifting a field / method (Pull Up);
  • Descent of the field / method (Push Down);
  • Replace Conditional with Polymorphism (Replace Conditional with Polymorphism);
  • and so on;
created: 2019-01-25
updated: 2021-03-13
132265



Rating 9 of 10. count vote: 2
Are you satisfied?:



Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Refactoring theory

Terms: Refactoring theory