Often times we find ourselves thinking in terms of "utilities". We have, for example, the great and competing lodash and underscore "utility" libraries, which have a bunch of "utilities". We also routinely include in our project a "utils" folder and put in there a bunch of utilities. And that's about it, not too much thought goes on about it.
Utilities (as we typically use them) are anything which is used, regularly or not. What does utility mean? A utility literally means something *useful*. It means something that you *use*. In essence, it is an action thing. Utilities as we use them today are basically a bunch of uncategorized and unabstracted actions left on the sidelines of our application.
Why is this a problem? The first part of the problem is that we are creating a junk drawer without a clear mental model. We are not using our mental powers to create useful abstractions. Instead we are just creating a pile of dirty laundry and keeping it as a pile. The second part is that we are losing out on great benefits in terms of developer tooling, productivity, and enjoyment. By thinking of things as utilities we are stuck in the past where these actions are not much more than functions exported in a module. Finally, the lack of standards in the structure of utilities means we lose out on automation and miss out on useful abstractions such as making it easier to build objects or do some sorts of actions from a DSL. In short, these 3 things mean we are just getting by with what we were taught through blogs and old practices, and not using our engineering training to our advantage.
The solution is first to make our utilities first class citizens. We can do this by calling them what they actually are, "actions", "functions", or "operations". I like the term "action" because it goes well with the term "object", which is their counterpart. But "operation" is a perfectly good word too. You should create an "actions" folder instead of a "utils" folder (or operations folder or whatever).
Second, now that you have some actions to work with, we can abstract them to become "serializable". Your actions take parameters, or payload if you will, which is simply a JSON object (or array, however you decide). The action is added to a global action dispatcher/store/whatever you want to call it, so all actions can be called from anywhere else. You can then take these serializable actions and write developer tools around them much more easily and more powerfully than you can module-exported utils. You can, for example, build a DSL for QA testers and integration tests to use to call into these actions using plain JSON (or whatever thin wrapper you add for DSL sugar). You can build UI time machine functionality to rewind and playback actions to aid in debugging and development. And you can easily log and trace anything by just wrapping your actions in a simple function. Basically, you gain the ability to move into the power realm of creating optimal developer tools that can call (the actions) directly into your application, with very minimal additional work.
Finally, gaining this structure and tooling, you can start automating a lot of your workflows like testing to a much greater extent. Instead of manual QA, you can just write out step-by-step actions to take in your app (which call actions/operations just like any other action/operation), and have your integration tests cover a great deal with, again, minimal overhead. In the end, you clean up a mess and start work with a better mental model to enable a greater developer experience, and ultimately ship higher-quality software faster.
What you should first do is rename all your utils folders to "actions". Then you should organize the actions into clear categories. In reality, actions can even be abstracted out into a third-party library or open source project if they are general enough. If not, then they should still likely be accessible from anywhere in the app. Then after you've renamed your utils folder to "actions", remove all your imports and replace the with a single line like `import actions from './actions'`. Replace your utils usage with `actions.doX` or `actions.call('doX', params)` or something like that. Make sure all of your actions start with a verb! Actions are verbs. Finally, if you would so like, make it so your tests and QA can call these actions directly in a simple API by wrapping your actions API in a JSON serializable DSL or data structure. Then you have one unified "action" system for your entire team end-to-end. This will take your code to the next level.