Jerome Thibaud logo
  • Home 
  • About Me 
  • Services 
  • Articles 
  • Contact 
  •    Toggle theme
    •   Light
    •   Dark
    •   Auto
  •  
    •   Light
    •   Dark
    •   Auto

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 words
Object Oriented Programming
 
Architecture
 
Object Oriented Programming
 
Architecture
 
Share via
Jerome Thibaud
Link copied to clipboard

I propose that Utils classes are an anti-pattern. I will explain why and suggest alternatives. Read this short write-up to learn more.

On this page
 

    • The Takeaway
    • The problem
      • The name ‘Utils’ adds no valuable information
      • Lack of discoverability can lead to duplication
      • Static on the line
    • Everything in its right place
      • Surface the information
      • Do not save on writing at the expense of discoverability
      • A few heuristics to put things where they belong
      • Use design patterns
      • Avoid static methods
      • When you don’t own the code
    • Conclusion

Utils classes considered harmful and how to do better

The Takeaway  

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.

The problem  

The name ‘Utils’ adds no valuable information  

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.

Quiz!  

Can you guess what you’ll find in myproducts/ecommerce/Utils.py?

Answer
 
The dragon you'll find in the Utils class

Lack of discoverability can lead to duplication  

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.

After a 3 min search for Utils on GitHub
After a 2 min search for *Utils* on GitHub, followed by a search for a random utils method's name in the same repo.

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.

Static on the line  

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.

Everything in its right place  

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.

It’s important to keep nuance in mind when applying any rules. There is typically more than one dimension to any problems. Optimizing for a single dimension in isolation might yield a poor outcome.

Surface the information  

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.

Do not save on writing at the expense of discoverability  

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.

A few heuristics to put things where they belong  

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:

  • What is the intent?
    • In what context do people typically do this?
  • If it was a role or job position, what would it be called?
    • what department would they work in?
  • What other concepts is this related to?
  • What are the relationships with the other concepts? generalization, specialization, association, composition, etc.
  • Does it apply to all objects of a certain type or only specific ones? How are they special?
  • If it was a task, who would be responsible for doing it?
  • What problem domain is this activity related to?
    • Does it fit into an existing standard classification (Taxonomy)?

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  

Use design patterns which are targeted at adding functionality to objects. For example, the decorator pattern .

Avoid static methods  

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.

When you don’t own the code  

This section is meant to be illustrative and is in no way an exhaustive listing of such techniques.

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.

C#  

When it comes to C#, the introduction of Extension methods in C# 3.0 allows for enriching existing classes with additional functionality.

Scala  

Also has extension methods

Python, Groovy, Javascript, Ruby  

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.

You should make sure that the code which performs the metaprogramming or the extension classes is discoverable as well.

Java  

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.

Conclusion  

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?

 How to decide to Build or Buy, now and in the future
How to deploy a Hugo site to S3 in 2024 
On this page
    • The Takeaway
    • The problem
      • The name ‘Utils’ adds no valuable information
      • Lack of discoverability can lead to duplication
      • Static on the line
    • Everything in its right place
      • Surface the information
      • Do not save on writing at the expense of discoverability
      • A few heuristics to put things where they belong
      • Use design patterns
      • Avoid static methods
      • When you don’t own the code
    • Conclusion
Let's Connect

Collaborate and create user value

       
Copyright © 2024 Jerome Thibaud. Licensed under Creative Commons (CC BY-NC-SA 4.0). Powered by Hinode  .
Jerome Thibaud
Code copied to clipboard