Tightly coupled software is like a product that requires other proprietary products
If you’re writing a library, try your best to decouple it from any other libraries. This has always been best practice.
Wherever you can, try not to force your users to use the equivalent of some unique proprietary cable that is required to charge a laptop, for example. Separate your library into components as much as it makes sense to and as much as you can bring yourself to.
Separation makes your software easier to test in isolation - yes, you definitely want this - and easier to maintain.
While this is something that’s preached over and over in the software development world, I think it deserves to also be said that it’s okay to have coupling, to push the idea of cohesion.
“Yes, I recognise this problem as two separate problems... that concern each other.” It’s important to recognise separate components that do a better job when used together. How they interact with each other is the important decision to make.
I stumbled across this issue while writing a router for my CMS framework. There is a clear distinction between the jobs of routing a request to some set of variables, and then using these variables to invoke methods and other magic things that return a response. But what use are these things individually?
While I haven’t begun to separate them again yet (before, they were separated but not in the best way), I know that I shouldn’t feel bad about having one hard-coded to rely on a default implementation of another, because it can always be replaced.
Before, my dipatcher required a router. I look back on this and understand that it’s backwards. Dispatching doesn’t happen first. I look at the problem now and think that a router should use a default dispatcher implementation (that exposes filters for matching callable routes if need be) and have said dispatcher overridden at instantiation if at all necessary.
I also look back on it and think, what was really that wrong about what I was doing? The dispatcher had exactly that; access to the router it needed. Instead of having to expose filters it could just assign them. Maybe I just preferred seeing “$router” and “Router” instead of “$dispatcher” and “Dispatcher”.
Software will always be a mess, but this post documents part of my several year long attempt at finding the best ways to minimise this mess.