How did we get rid of messy utils file?

Photo by Tom Wilson on Unsplash

This is the story of how we got rid of the messy utils file we had in our mobile codebase at Bayzat. We hope, it will inspire people to be more conscious of what can be improved and the kind of benefits can be gained in the case of improvements.

Awareness

The time you feel discomfort about a part of your codebase, can be the beginning of something good. If you realize that you feel discomfort about the way a thing is handled, odds are high that you will be seeing rooms for improvement after you rationalize the reason of discomfort and examine that part of the codebase deeper.

You may transition through a set of emotional states while examining the code. You may be surprised about how the things were handled, horrified when you see how all anti patterns were applied, or superior, since you feel like all engineers other than you, did not do anything to make this part of the codebase better. However, by only looking at the source code, it is not possible to understand all the factors that form it to how it looks like today. Perhaps, there were no engineers as conscious as you, that mentioned the problem or provided a solution for it. However, it is also perfectly possible that, the reason why you can see lots of improvements at the time, is the improvement efforts made by previous engineers who waded through lots of challenges. Since it is not quite possible to know the real story by only looking at the latest version of the source code, it is much better to think positively and focus on the rooms for improvements.

After the examination process, you may have already a solution in your mind to eliminate your discomfort, which is great. Yet, it is always better to discuss this problem with people in your team and also understand what they think, learn more about the different solution ideas and also see whether people feel the same discomfort as you do. If you do not feel like people are not worried or do not care as much as you about the problem, be well prepared to explain your concerns and the benefits can be gained in the case of improvements. Crown your awareness with a perfect rationale.

Our case

I remember, in our Flutter codebase, every time I put a function into the utils.dart (consists of only top-level functions) over 1.5k lines, I was feeling bad. When you think more about this feeling, you understand, why it gives you pain:

  1. I realized that this file under the core directory was containing common functions to be used by all modules but also some domain specific functions which does not make any sense. Since the file was like a building with broken windows, it was apparently not incentivizing developers to put these domain specific functions into a more suitable place or even search for such place. People were breaking more windows by not caring the reason of existence of this file, since this reason was overshadowed by the building’s condition.
  2. This file was huge and it was containing functions with completely different purposes in a mixed way, so it was not easy to find the function that actually meets your demand. The developer was searching the file with the name of the function, which would be a good choice for a function with that purpose, and finds it or not. The feeling of dealing with a file which looks messy, was actually decreasing the effort put for the searching. If developer could not find the function and decided to implement a new one, there are two cases that could happen:
  • There was actually no function that could satisfy the demand of developer, so one is introduced. We hope that it has a name that matches with what the other developers will search for in the future. Meanwhile, the host functions in the file welcome their newest friend.
  • There was actually a function that satisfies the demand, but developer could not find it. So, there are actually two functions now that do the same job, which hurts the consistency in the codebase. Also, developer put efforts into a brand new implementation, which was just a waste of time.
The host functions in the utils file welcome their newest friend

I presented this issue to the team and proposed a possible solution for improvements. After discussing different ways to handle this issue, we determined a solution to be implemented.

Planning & Action

It is important to understand what the determined solution requires to be implemented. For instance, if it requires dedicated time, this needs to be provided, or if it requires several engineers from different teams to be involved, such alignment needs to be planned. Understanding the next steps helps to be aware of the feasibility of the solution, and allows you to update it, so that it becomes feasible enough to see the development roadmap clearly.

Our case

utils.dart is divided into cohesive classes

The determined solution was, dividing the messy utils file into cohesive classes where each class contains static methods that serve similar purposes. Our main decisions regarding with the implementation of the solution were:

  • utils.dart will be divided into cohesive classes.
  • If a method is domain specific (performs specific actions which will only be needed by specific module), it needs to be moved to the utils class of the related module.
  • Common utils classes that contain utils methods to be used by all modules will be put under the core directory.
  • While moving the methods into related classes, they won’t be refactored to not break anything and keep the process simple.

The aim of the solution was getting rid of utils.dart as fast as possible, by not refactoring existing methods or eliminating the methods that serve similar purposes.

Implementing this solution was determined as a mobile architecture objective among the other objectives determined to be accomplished during trimester. Since people were adding functions to the utils file during the feature development process each and every day, having a dedicated time for this task would ease the process of getting rid of it and merging the new structure to the main branch quickly so that everybody can develop on top of it. The hack week, arranged to allow development teams to focus on their technical debts, gave us a helping hand to implement the solution without a lot of drama.

Effects

After the solution is implemented and merged to main branch, all developers will be basically developing on top of it. So, consider the time of the merge as the release of your product and monitor it as time goes by. Answering these questions can help you to understand the effect of the solution and what can be done more:

  • Do you see any improvements in the codebase?
  • Do you see any deteriorations in the codebase, if so what can be the root cause, a broken window left or a new one occurred?
  • Do you still feel discomfort in the same part of the codebase, if so what can be the reason?
  • How developer experience is affected by these changes?

Answering these questions should be on a constant basis since your answers will evolve as the new things you learn everyday change your perspective on the problems. Also, since the software evolves constantly and needs care, detecting cracked or unnoticed broken windows earlier, can increase the software quality if necessary actions are performed.

Our case

These are some of the effects of our solution:

  • Developers started to search for utils class that may contain a method that performs the action they want.
  • Developers are able to check easily whether the intended method exists in a small and cohesive utils class. This improvement should increase the consistency in the codebase as time goes by.
  • Developers started to create new utils classes if suitable one does not exist.
  • Since, the methods and their utils classes are much more visible and properly grouped, it incentivizes us to write unit tests to increase the testing coverage of these methods. We have now files named as string_utils_test.dart that contains tests to cover all methods in StringUtils class. Before, it was not obvious to us what we have as string utils methods to be tested, since they were lost in the ocean of utils.dart.

What’s next for us?

As mentioned before, in order to keep things simple, we postponed several improvements during the implementation phase. So, the next steps may include:

  • Eliminating methods that serve similar purposes, to increase the consistency in the codebase.
  • Refactoring the methods in the utils classes and writing unit tests for them.
  • Replacing some of the utils classes with the objects to make use of OOP better. (This article is worth reading).

Although some of these steps seem like must, our priorities may change according to our observations or decisions about what can work for us better and has higher impact. We will be monitoring the codebase, trying to understand what can be improved more and prioritizing the issues we need to solve to get the most benefit.

Notes:

  • In order to give a shared prefix to a group of methods, Effective Dart suggests to put top-level functions in a library and use it with import prefix instead of using a class with static methods. At the moment, class with static methods approach seems easier to manage for us, since we do not need to worry about the import prefixes every time. Everybody will just use the specified name of the class and that’s it. You can see the various methods available in the class after you write the name of it, with the help of the IntelliSense feature of your IDE which sometimes saves you from navigating to the utils class to see the methods available. This is also possible with the library approach using import prefixes, but as mentioned, we do not want to manage it currently.
  • Extension methods can also be used to replace some utils methods, but they are not easy to use since there is an open issue about not being able to import them automatically. Basically, the extension method that you may need is not suggested to you directly which hurts the developer experience a lot.

In future, we may migrate our several utils classes to different approaches to see how it goes. (e.g. how it affects the developer experience). We love experimenting.

Final words

Refactoring should be on a constant basis. We need to care the codebase we deal with everyday, try to understand how it evolves as the world changes and what needs to be done to prevent the rot.

Question the codebase you work on, be the action starter, share it with people and monitor the changes.

Software Engineer at Bayzat