Utils classes considered harmful and how to do better
Posted on March 11, 2024 (Last modified on April 12, 2024) • 6 min read • 1,142 wordsI propose that Utils classes are an anti-pattern. I will explain why and suggest alternatives. Read this short write-up to learn more.
Instead of creating or adding to a ‘utils’ class, stop and consider the problem domain of the functionality. Choose an informative name and locate the code near the relevant domains. Leverage Design Patterns and language features to increase usability.
This will better document the intent and the existence of the functionality to your coworkers and future self.
Utility: the state of being useful, profitable, or beneficial.
If a piece of code is not useful, delete it. If it is useful, how does it contrast with the rest of your useful code?
Qualifying something as Utils in the context of an application is akin to saying Miscellaneous. Source code is read more often that it is written and such name does not benefit the reader.
Can you guess what you’ll find in myproducts/ecommerce/Utils.py?
A corollary to the lack of information is that functionality in a Utils class is harder to find than functionality in a properly named domain type. A developer in a hurry could miss the existing method and create their own duplicated method.
And yes Helper classes are in the same shady business.
Note that this is the natural consequence of normal people fighting an impossible battle against time and ever growing complexity. That is why following good programming patterns help keep everyone safe.
Very often, those utilities will be provided through static methods. It’s due in part to the fact that the functionality is out of context (not in the type that it targets).
Static code brings challenges to testability and flexibility. It effectively breaks dependency injection and does not allow for substituting implementations in testing, during local development, etc.
Some of these limitations can be alleviated through tooling (e.g. in Java, Mockito now allows you to mock static methods), however this increases the ongoing complexity of dealing with the code.
We can reduce the likelihood of bad things happening by challenging the need for a Utils class and thinking about how to create predictable order.
If you think about the typical coding environment, the first thing you will see is the project or module name. Next are package names, then the class names and finally method names, the last step before having to read the code statements.
You have an opportunity to bring information to the reader at each of these levels. The earlier you do, the faster the reader will be able to understand and take action. Try to provide the most information at each level.
an example:
mycompany.product.domain.subdomain.TypeInThisDomain.associatedCapability(...)
more examples:
String.format(...)
or StringFormatter.format(...)
better than StringUtils.format(...)
better than Utils.formatString(...)
Request.GetHeader(...)
better than HttpUtils.GetRequestHeader(...)
better than Utils.GetRequestHeader(...)
encoding.Unicode.parse(...)
better than encoding.Utils.parseUnicode(...)
etc.
You may think that 1 less package or class will save time. However, not providing the context of a package or class name will likely cause readers to have to scan through 4 times as much code to find what they are looking for.
There are whole theories and methods about how to organize things ( Ontology, Taxonomy, Domain Driven Design, etc. ). You should certainly learn about them but in the end,
it boils down to organizing things so that readers can reason about where they are and what they do.
To find where you should put your code, I suggest asking yourselves (or your favorite LLM) the following questions:
Answering these questions and drawing parallels with other types of organizations people are familiar with, will help you determine the best module, package, class and method location.
Use design patterns which are targeted at adding functionality to objects. For example, the decorator pattern .
Support testability and flexibility by avoiding the use of static methods. If the functionality does not belong to the targeted type itself, create an object whose purpose is to perform the action on the object e.g. a parser, a formatter, etc.
One of the forces leading to the creation of utils class is when the function you want to add belongs to a domain or class in an external library. You can’t modify the original type so you create a new class (e.g. StringUtils, SomethingUtils, etc.). 2 language features to look for in this situation are extension methods and metaprogramming.
When it comes to C#, the introduction of Extension methods in C# 3.0 allows for enriching existing classes with additional functionality.
For those languages, Metaprogramming will allow you to more easily put your code where it belongs. For example in Python, the Metaclass facility allows for modifications of the type.
Accessing such capabilities is a little bit more complicated in Java but can be achieved through external libraries, for example the Manifold compiler plugin. Reflection and Aspect Oriented Programming are also available but add a fair amount of complexity.
Spending the extra 1 to 10 minutes thinking through and refining your design will likely save a great deal of time in the future. Remember that code is read way more often than it is written. Whether you’re doing it for the future you, your colleagues or the next generation of engineers on the project, ask yourself:
How would those people find your nice function? How would they know it exists? How can I make my code more usable?