One of the key benefits of low coupling is that it reduces the number of things you need to consider simultaneously to understand a single component of a system -- allowing among other things for easier debugging, easier maintenance, and better extendability. A reasonable way to answer your question is to consider whether or not SRP would improve your code with respect to these metrics.
My claim is that SRP has the ability to reduce coupling, but that reduced coupling is not guaranteed. I'll illustrate this with two scenarios.
Scenario 1 (don't do this)
- Start with one mega function
foo()
.
- Note that
foo()
violates SRP, so re-factor it into bar()
and baz()
.
- As your application grows, other parts of the code wind up depending on
bar()
and baz()
.
- Customers and use cases change over time, so eventually
foo()
needs a modification. Most of the logic in foo()
is implemented in bar()
and baz()
, so to change behavior in foo()
we change the behavior in bar()
and baz()
instead.
- Now everything breaks.
Even though SRP has the ability to reduce coupling (at least I'm claiming it does), in this example the programmer is treating foo()
, bar()
, and baz()
as a single unit AND they have the additional complexity of that unit being split between multiple function definitions and maybe even multiple files. The SRP was in name only and had no real benefits because of how the functions were being used.
Scenario 2 (reduced coupling with SRP)
- Start with one mega function
foo()
.
- Note that
foo()
violates SRP, so re-factor into bar()
and baz()
.
- As your application grows, other parts of the code wind up depending on
bar()
and baz()
.
- Eventually
foo()
needs a modification, but we don't have an appropriate low-level component lying around with the desired functionality, so we make a new function bizz()
.
- Now
foo()
consists of bar()
and bizz()
, and everything else still works fine.
In this example, it is easy to reason about the behavior of foo()
because we're treating its two constituent parts as atomic. They have a well-defined behavior, and combined they make up foo()
. When those behaviors are insufficient, we make a new atomic component with the desired functionality. This approach easily leads to code re-use and prevents API-breaking changes. Moreover, all the components are small enough and can be easily reasoned about and proven to be correct.
Extras: Even though SRP can decrease coupling in a sense, if functions are not grouped appropriately it has the potential to have every module in an application depending in some way or another on every other module. Designing the entire system to be loosely coupled is every bit as important as designing functions to be loosely coupled, and for roughly the same reasons.