In the life of every programmer, there is a moment when they were tasked with adding one final feature to a project just before its release. Usually, there is no time to do it appropriately and in a way that lines up with the rest of the codebase. It is almost inevitable that sloppy code will appear, but that does not mean you have to tolerate it.
Refactoring your code wherever possible is a worthwhile practice that will assist any project you work on in the short and long run. It is a practice that we do regularly at Sunscrapers on our projects. In this post, I have provided a real-world example from our CTO - Przemek Lewandowski.
What is Code Refactoring?
Code refactoring is a method in software development that involves cleaning and restructuring existing code without changing its function. Code refactoring brings many benefits, including increased readability and reduced complexity. As a result, we can improve the code's maintainability and make it simpler and cleaner.
According to Martin Fowler, author of the book Refactoring, Improving the Design of Existing Code
Although the primary goal of refactoring is not to eliminate errors, it does help prevent them in the long term. Refactoring also helps in the elimination and reduction of technical debt. Without code refactoring, developers can easily become snowed under with it.
When should you not refactor?
Refactoring is very helpful when we are working on long-term software. It helps the software to be more adaptive. However, you should avoid performing refactoring if it:
- Exceeds your budget
- Exceeds your time
- The code is in production
- The code meets all required standards
- There are no security-related issues present in the code
- Risks and benefits are not taken into account.
When should you consider software refactoring?
There are at least several different approaches when refactoring should take place. Refactoring should, in an ideal world, be an integral element of software development rather than a separate process.
- For many developers, the best moment to refactor is before working on a new version of the software or before adding new functionality. Cleaning up the code before adding new features to it improves the quality of the product itself and makes the job easier for other developers.
- Another approach to refactoring is to proceed with it right after the product launch. Software programmers then have more time to clean and restructure the code.
- Bug fixing is a perfect opportunity to work on code refactoring. You may find bugs in the dirtiest chunks of code. By doing refactoring, you are not only cleaning code, but you may find that some bugs may nearly fix themselves.
- Code review is an extremely important stage of the programming cycle. It helps with finding defects of the code, knowledge transfer but also improves code quality. It is also the last chance to refactor and fix the code before it is released.
How to perform code refactoring: the main techniques
Before we even start the refactoring process, it is good to think through what and how we want to change. The process helps to prevent an uncontrolled code extension. Keep in mind also the following tips:
- Deadlines - if you do not have enough time and the release is around the corner, it is better to focus more on things like debugging than refactoring.
- Large pieces of code - working on them requires good management and planning for the entire team. You should be aware that refactoring large pieces of code may be blocking tasks of other developers.
- Communication - if something gets out of hand do not hesitate to communicate with the team and the project manager. Communicating with them may allow you to solve the issue more efficiently, rather than trying to solve it all by yourself.
- Test & Review - while working on refactoring, write a test for your code and ask other developers to review the code design. The first one will help you to control changes that occurred in the process, the latter helps with verification of the flow and proposed changes before they are implemented. It is also a great way of knowledge transfer between the programmers.
There are various refactoring techniques. Some of them may only apply to certain languages or language types. Covering all of the techniques would take more than one post. Below you may find the most popular methods:
Red-Green-Refactor is an Agile engineering pattern with Test Driven Development (TDD) approach.
- Red - think what has to be developed.
The purpose of the first phase is to write a test. The test is meant to fail because the code for the tested functionality does not exist at the time of writing. The test will only pass when the expectations are met.
- Green - think how to pass the tests.
In the green phase, you should write just enough code to pass the test. The code does not have to be extremely elegant in this phase. Let us leave it for the next phase.
- Refactor - think how to improve an existing implementation.
The final and the most important phase. At this point, you have to refactor the code as well as the tests. While doing it, you should focus on the contents and look for methods to improve it.
Take into consideration the following things:
- Is the code following a proper style?
- Are there repetitions?
- Does the class have a single responsibility?
Branching by Abstraction Refactoring
Abstraction Refactoring is a technique that is typically used to make a large-scale change to a software system in a gradual way. The main concept is to create an abstraction layer that wraps part of the code that will be refactored and the counterpart that will eventually replace it. This technique allows developers to release the changes regularly while the changes are still in progress.
Pull-Up/Push-Down method is a great example of Abstraction Refactoring. These are opposite types to refactoring with classes. To avoid code duplication, the Pull-Up method pulls code parts into a superclass. The code is pushed down from a superclass into subclasses using the Push-Down method.
Compose Method is a fundamental refactoring pattern that should be in everyone's toolbox. Longer code is more difficult to understand and implement.
The composing method helps to reduce duplications and streamline a code. This can be done in several ways, including through extraction and inline techniques.
The older the code, the more fragmented and difficult to read it becomes. As a result, going in and simplifying a lot of the logic makes sense. The goal is to simplify method calls and the interaction between classes. This can be accomplished in a number of ways, including the consolidation of conditional pieces and expressions and the substitution of polymorphism for conditional.
Simplification may include adding, deleting, and introducing new parameters, as well as replacing parameters with explicit methods and method calls.
Moving Features Between Objects
The goal of moving features among objects is to deal with distributed functionality across different classes. This method involves the creation of new classes as well as the transfer of functionality between the old and new classes.
When one class becomes overloaded, it is time to distribute some of the code to another. Alternatively, if a class is not doing too much its features can be moved to another class and the class can be deleted.
Refactoring Legacy Code
Sometimes there is a need to refactor existing code that was created by somebody else. There may not be enough regression tests to be sure that we won't break anything eventually.
To face legacy code it is worth applying the aforementioned TDD method. However in this case usually we need to add a few more regression tests to existing code and make sure they pass without touching the original code yet. In fact this practice unfolds the safety net before the actual job.
Here is a short checklist that may be a good starting point to technique described above:
- Check if the code that is meant to be refactored is well covered by tests. Look for testing edge cases, not only a happy path. Pay attention to integration and/or e2e tests. After the refactoring process, all user functionalities should work exactly the same. Implementation details may differ so some unit tests may need to be updated after refactoring.
- If code is not enough covered by tests then write them first before even starting the refactoring process.
- Do the actual refactor. It usually improves code structure, data flow, performance, cohesion or decoupling.
- Improve the code until all regression tests pass, especially integration and e2e.
- If the code still needs refactoring, go to point 3.
Automated tools may help
Many code editors and integrated development environments (IDEs) can easily automate refactoring. Developers can clean up the code by applying refactoring when the need arises as a normal part of implementing tests and code. The feature is incredibly helpful for agile developers who need to arrange refactoring faster. This includes extracting code to a new function, method or even class with just a few keystrokes.
Here are some examples of so-called refactoring browsers:
- Visual Studio
You may read more about the above IDEs in my article about the best Python IDEs and code editors.
Code refactoring may help you to keep your code clean and in place. A well organised and refactored code brings you less stress – it is easier to find things and operate it. It also makes things easier for a person who overtake the project. Refactoring helps programmers to understand the code better and make better design decisions.
Regular checkup and cleaning of the code will reward you with creation of a healthy, more productive workplace and working routine.