In this article, I would like to present the problem I have encountered along my programming way, its solution and the refactoring results.
Have you ever had an experience like this? Quite often there are situations when part of the application appears multiple times, most of the usages are the same but they require different logic here and there. Sounds familiar? I have also faced a similar problem and decided to take a closer look at it. The photo shows exemplary usages of Metric Forms with different levels of complexity.
As you can see some of them are pretty easy and some are more complex. In my case, such forms appeared about 20 times in the app and most of them were more or less unique. They were handled by one poorly configurable component and a dozen of “one-off” components. While working on such form one has to dig through custom or spaghetti code and the changes which don’t apply to other forms. This often leads to inconsistencies in design and lots of bugs. But the most important issue was, that with every new feature, number of Metric Forms in different shapes and forms grew. The problem was getting worse and worse.
You won’t hit the target unless you aim for it
So I got down to work and extracted 3 main requirements that new solution should fulfil. It should be done because refactoring of code used so many times should be done with caution. Firstly, the new solution must be configurable - should allow the developer to build the form structure he needs at the moment. Secondly, it must be modular - should allow functionalities to be easily extended and added in the future. Thirdly, it must be feature-rich - should contain shared logic so it does not need to be duplicated every time. This one can be dangerous because more and more logic increases complexity in a highly reusable component which can get out of hand.
With Lead Developer Tomasz Bąk, who is my workmate, we came up with a solution to extract Metric Form fields to separate Field Modules so we could build forms out of them similar to LEGO’s.
The photo shows the Field Modules in the first part of the process.
What is very good about it, the selection of fields and their order takes place by passing an array of objects. Every field contains only its own logic. We are not restricted to extract fields, but can also extract groups of fields. Adding new field across the app is just the matter of creating its component and defining in the configuration that it should be used in the given place. Metric Form itself handles lots of stuff which is not assigned to a specific field like saving, reordering or building the form from the blocks.
The photo shows the Metric Form list - the second part of the solution.
Pros and cons of my solution
Like everything, the implemented solution has got its advantages and disadvantages. Let me focus on pros first.\ Configurability which can be described as building any form one wants by just providing configuration object. Separation of responsibilities which means that every field component contains only its own logic. Shared logic done once. Forget about repetitions. Changes will be available everywhere. Saving, reordering or validation is done automatically which can save a lot of time. Modularity should be mentioned here. A new field can be added without problems whenever it is required. A decrease in the codebase. In my case, it was something like 5000 lines of code less. Not to mention, countless bugs prevented along the way.
However, there are of course some negative aspects as well. Quite long and time - consuming process. As for me, planning the whole process, implementation and replacing usages took about 3 weeks. Taking into consideration the effort one has to make, it should be clearly stated that it is not an easy task. Complex field interactions which sometimes can interfere and cause infinite loops or another unexpected behaviour. Require caution when extended. One has to remember that any change working in one place might break others. It is because of the fact that it is used in many places. Does not cover all use cases. Abstracting out logic increases complexity and handling edge cases will add a lot of it for minimal gain. Harder to test and check by Q&A. Metric Form List being abstracted out and used in many places require QA to check all of them.
A feature which appears many times in the app and needs different logic tends to be problematic. The solution to divide Metric Form fields into separate Field Modules and wrapping them into one Metric Form list not only does it significantly speed the development but also makes the code shorter.
If you need help with your Front-End development, check out our experienced team!
PS. Here's a link to the presentation on YouTube :)