Are you ready to take your front-end project to the next level and improve the maintainability and performance of your codebase? In this article, we will guide you through the process of refactoring a front-end codebase. Discussing the importance of understanding technical debt, assessing the current situation, planning a refactoring strategy and highlighting some techniques.
Refactoring in web development is the process of improving internal code structure and readability without changing external behaviour. By reorganising existing code, developers are able to produce more reliable programs while cutting down on development time.
Refactoring can help developers better understand existing code, reduce the complexity of the code, and make it easier to maintain. It can also help to reduce the number of bugs and improve performance.
Often this means switching frameworks. Selecting a new framework for your front end can be a double-edged sword. While it can bring new features and performance enhancements, it could also introduce new bugs and necessitate additional time and resources for implementation. On the other hand, retaining your existing framework can provide reliability and familiarity, allowing you to focus on other areas of growth. However, you may miss out on the latest features and performance enhancements that a new framework can offer.
To weigh the pros and cons of selecting a new framework or retaining the existing one, you must consider the benefits of each option against the potential drawbacks. Ultimately, the decision should be based on your team's ability to address potential issues, your project's requirements, and the long-term goals of your organisation.
The first step in this process is assessing the current situation. The main reason a codebase becomes unmaintainable is technical debt.
Technical debt is often the result of making decisions that may seem convenient or expedient in the short term but can end up costing significantly more time and resources to fix in the long run. For example, cutting corners to make a deadline could result in technical debt that requires more effort to repair than it would have taken to do it correctly from the beginning.
To get a better picture. Ask yourself the following questions:
What is the quality of the current codebase?
- Is it easy to introduce new features on the current codebase?
- What are the bottlenecks during development?
- Are they process and/or code related.
- How is the test coverage?
- Can you refactor with confidence?
How is the current developer experience (DX)?
- Is it clear how to set up and run the project from documentation?
- Is there a fast feedback loop?
- Is linting, formatting and type checking present?
- Is the current tooling relevant and/or up to date?
- Are there documented coding guidelines?
- Are the quality checks set up at the correct moments?
What is the current state of the dependencies?
- Can we easily update them to their latest versions?
- if not, why?
- Are there potential security risks and/or unmaintained 3rd party solutions?
Another way to assess your codebase is by calculating the Technical Debt Ratio (TDR), a metric used to predict the potential cost associated with technical debt. An optimal TDR is generally around 5%.
Depending on the current situation, you have the following possibilities.
Continue with the current codebase
It seems straightforward, but this is often a viable option. Pause and spend some time fixing problems that were identified during the assessment, such as
- updating dependencies
- update and/or switch tooling
- improve DX by improving the speed of the feedback loop
- improve confidence by adding tests
Additionally, establishing a technical debt registry can help you monitor technical debt problems, elucidate their implications, and recommend solutions to address them.
Actively plan and work on these items.
Build a completely new application starting from scratch, often paired with a "Big Bang" release. This approach is often paired with a lot of FUD (fear, uncertainty and doubt). Development of new features will need to be temporarily halted.
This solution is quite drastic, and there are almost always better options.
This is, in most situations, the best path. We're not starting from scratch, but we're looking for ways to develop or refactor features while not halting progress.
2 applications, embedding the legacy application in the new one
Set up a new application in your preferred framework. Recreate UI elements that are shared over multiple pages in the new application (header, footer, ...).
When you navigate to a page either- render the new page in the new application framework - fall back to the legacy page in an iFrame
The legacy application should load in a chrome-less mode when loaded in the iFrame. You can communicate between the legacy and new application using postMessage (make sure the applications are on the same domain)
Maintenance work can still be done on the legacy application, while you can gradually refactor and introduce new features in the new one.
The biggest benefit of this approach is that the legacy application will disappear over time.
This approach works bests in scenarios where you're refactoring a SPA to a new framework (Angular to Next.js, for example). If the legacy application heavily depends on server-rendered data and/or has a lot of callback mechanisms in place, the next technique might be more appropriate.
2 applications, side by side
Set up a new application in your preferred framework.Recreate UI elements that are shared over multiple pages in the new application (navigation, chat etc..).
You now have a new and old application that will be served depending on the URL.
Host it on a subdomain or under a subpath (don't forget to add rewrites if needed)
When you navigate to a page either- load an old page in the legacy application- load a new page in the new application
This setup introduces some duplicate code (header, footer, ...) but removes the complexity of having an embedded application.
How to start with refactoring
Careful planning of the refactoring and having a timeline is essential for a successful implementation. This includes creating a list of tasks, augmenting test cases, consistently testing, and involving the QA team to ensure that the refactor is effective and does not introduce new issues.
You can either go for a safe approach by prioritising low-impact features or go for high-impact by prioritising important features.
Introduce feature flags in your codebase to gradually roll out refactors internally first. This technique decouples deployment from release and allows you to test on production before making the changes available to everyone.
In conclusion, refactoring is an essential process for maintaining and improving your codebase, ensuring that your projects remain compliant with current code standards and technologies. By understanding technical debt, assessing your codebase, planning a refactoring strategy, and implementing and testing, you can effectively address technical debt and enhance the maintainability, performance, and readability of your code. With the right techniques, you can create a more efficient and manageable codebase, paving the way for future growth and success.