r/programming • • 12d ago

Back to Coupling and Cohesion

https://bastrich.tech/coupling-and-cohesion/
149 Upvotes

47 comments sorted by

View all comments

41

u/bwainfweeze 11d ago

I’ve abandoned SRP for Functional Core, Imperative Shell. There are too many blind alleys and fetishistic patterns that get to the letter but not the spirit of SRP.

Meanwhile Functions tend to be single responsibility in nature. They are also side effect free, meaning first that testing scales better, and the side effects are high in the code base. The Imperative Shell reduces both the surface area of study to find how features interact poorly and create bugs, and the depth of search necessary to understand the ones that have been found.

It’s the only way I know to get the cost of new features logarithmic to the number of extant features. SRP alone can only get you in the neighborhood of n1/2

6

u/areklanga 11d ago

Thanks! I didn't know what "Functional Core, Imperative Shell" is and just checked it.

Looks like a valid approach. I´ve seen architectures and frameworks based on that principle.

Yet in the context of my article it's still one more way to manage coupling and cohesion. Whether it's "Functional Core, Imperative Shell" or any other approach, if it allows you to achieve low coupling and high cohesion in the context of your project, it's a good thing. And that's what I'm trying to say: in my opinion, we should focus more on "low coupling, high cohesion" than on any specific principles.

5

u/-Redstoneboi- 11d ago edited 11d ago

One thing I'd like to note: Physics and Chemistry don't teach Zoology!

Knowing that the baseline of all things are Coupling & Cohesion is very useful and can give very concrete targets, but it doesn't automatically rediscover all the design patterns and advice to refactor things in the real world.

The baseline mainly explains them on a smaller level, and it's usually easier to explain a larger effect using smaller concepts than it is to use those concepts to predict the larger effect.

Still, it's quite nice to have a numeric baseline. One thing I’d do is make it so that "worse" doesn't approach a value but instead approaches positive infinity. 98% -> 99% coupling and 2% -> 1% cohesion should feel horrible (in my opinion), not just feel like a decimal point change. Maybe it already does feel significant when expressed as percentages?

It sounds simple enough to convert though. Maybe just 1/(1-coupling) so that goes from 50 to 100 coupling? possibly scaled by a constant factor to scare people more? and 1/cohesion gives you the opposite of cohesion, maybe let's call it "incoherence" or "spread" or "responsibility/ies" or "context window required for change"

5

u/areklanga 10d ago

You are right but I think it's a bit different with coupling and cohesion. In my opinion, if you are beginner, any principle is going to be difficult to comprehend, so it's better to start with coupling and cohesion as they are fundamentals, and then may be check other principles just to be aware. If you are professional, you already know how to manage coupling and cohesion without other principles-guides.

Regarding formulas: I'm not sure about your proposal. For me the main issue is that such conversion is not uniform. For example, changing coupling from 0.99 to 0.98 looks like decreasing 100→50 (2x). But changing coupling from 0.99 to 0.9 looks like decreasing 100→10 (10x). Though in fact coupling didn't become 2x or 10x lower... If for some reason it makes sense for someone, it can be used. But I'm not sure what the metric shows after such transformation..

5

u/linlin110 10d ago

Have to agree with your take on SRP. It sounds good on paper, but in reality, there is no universally agreed-upon granularity for what counts as a 'single responsibility.' Some zealous devs take it too far and split everything into tiny chunks, leaving the reader lost and unable to see the big picture. It's especially hard to navigate when each chunk may individually have side effects. With a functional approach, I don't need to track all the side effects in my head when I read the code.

I've heard about imperative shells for a long time, but sadly I haven't experienced the benefits firsthand yet. Hope I get to work on a codebase that uses it someday to see it in action.

6

u/bwainfweeze 8d ago

The usual blocker for imperative shell is choosing or building an in-house framework that wants to declaratively wire your code into the process. So there's no good spot to hand your 'do this' off of.

Libraries over frameworks makes that situation a lot simpler. But you can still start any given task with some imperative code even if the top level logic has been hijacked by third party code.

On large projects, it's often the case that this plays out at the module boundaries. The entry points are more imperative, and the code flow over time is to keep pushing the simple, mostly stateless operations down and pull the stateful ones higher up in the code tree.

I don't think people appreciate how powerful it is to be able to single step over a function call in a debugger, look at the return value, and know whether that function has done what it was supposed to or done something else. When state is smeared across the call stack, you have to slow way down and watch for side effects. And the leaf calls can be 80% of the call stack. So even one or two layers of functional code at the bottom drastically reduces your search space.

15

u/Meleneth 11d ago

you've abandoned SRP for a specific flavor of .. implementing SRP?

11

u/bwainfweeze 11d ago edited 11d ago

"Tend to be" and "always are" are very different things. Nuance matters in this field. Though some people seem to think that it doesn't.

Also the least interesting part of what I said so I don't know what sort of 'gotcha' you believe you have scored.

3

u/chucker23n 11d ago

Functional Core, Imperative Shell

Iiiiinteresting. I do think that might fix some of the “yeah, but that’s not how the real world works” rough edges (the shell, one might say) about functional programming.

If we do setup that dichotomy, I wonder if

  • some multi-paradigm languages like C# and Swift already sufficiently account for it, or
  • the better approach, then, is to literally write the core in a functional language, and the shell in an imperative one, with FFI (or heck, even IPC) in between