If you have ever worked on software projects, you probably have seem before a folder named "helpers" or "utils."
These folders typically store auxiliary functions that do not fit into the system's modules. A common example is a utils.js
or helpers.js
file containing functions such as:
// utils.js
export function formatDate(date) {
return new Intl.DateTimeFormat('pt-BR').format(date);
}
export function generateRandomId() {
return Math.random().toString(36).substring(2, 15);
}
When I first learned about this, it initially seemed like a great way to keep reusable functions in one place. That’s why I was surprised when I saw a tech lead strongly advised against their use. When I complemented his explanation with a lesson on Cohesion in Software Design, the reasoning seemed worthy of being shared.
The problem of low cohesion
Cohesion, in software engineering, can be understood as the measure of how related and focused a system component’s responsibilities are.
High cohesion is a desirable quality in software design because it promotes maintainability, reusability, and code readability. [...] On the other hand, low cohesion indicates that a module’s responsibilities are loosely related or scattered across various functionalities. This can result in code that is difficult to understand, maintain, and enhance.
fonte: startup-house glossary
Excessive use of helpers and utils may indicate low cohesion, as it suggests that functions have been isolated without a logical grouping. If a function does not fit into any specific part of the code, this may indicate that:
- The system’s design lacks clarity.
- Perhaps this functionality should not even be included in the project, as its scope of responsibility is too different.
- The function could be reconsidered to integrate into a specific module instead of being placed in a generic folder/file.
Example:
Imagine we have a function that generates random passwords, and we place it in utils.js
:
// utils.js
export function generatePassword(length) {
return Math.random().toString(36).slice(-length);
}
If this function is only used within the authentication, it would make more sense to move it to the authentication module:
// auth/passwordService.js
export function generatePassword(length) {
return Math.random().toString(36).slice(-length);
}
Suggestions
- Question whether the function should actually be in the project: If a function seems so disconnected that it does not fit into any specific context of the code, a good question to ask is: does it really belong in this system?
- Create reusable packages or libraries: If the functions are very generic and reused across multiple projects, it may be worthwhile to turn them into an internal or public library. This improves modularity and maintains cohesion within the core code of each project.
-
When the use of utils is unavoidable: In some cases, having a small collection of utilities may be inevitable. The key is to minimize this practice and ensure these files do not become a "dumping ground" for miscellaneous code. If you need a
utils
folder, keep it concise and well-documented.
Conclusion
Using helpers
and utils
folders may seem like a quick solution, but over time, these folders can become disorganized repositories of generic code, making maintenance difficult and indicating structural issues in software design. The ideal approach is always to question whether a functionality truly belongs in the project and, if so, find a location where it maintains cohesion with the rest of the code.
Instead of asking, "Where do I put this generic function?", the better approach is to think, "Why does this function not fit into any module of my system?"
Especially if your goal is to build software with clean architecture, every piece of code should have a clear and meaningful place in your system.