by bccdee | 2025-11-16 22:41:44+00:00
> I feel like if we argued over a beer we’d probably end up agreeing on a lot of things. But let’s start by disagreeing. :-)

Definitely! :D

Re: SRP, my issue is that it's too subjective. Even for a very simple code snippet like `foo.x = bar.x`, it's not clear whether this covers one responsibility (copying bar's x to foo) or two responsibilities (retrieving bar's x and then setting foo's x). An experienced programmer has enough intuition to understand which of these options is better based on context, but SRP is supposed to be guidance for novices. Maybe I'm asking too much from a one-sentence piece of advice, but maybe splitting up code well requires more than one sentence worth of guidance, and I think SRP boils things down beyond the point of usefulness.

SOLID was invented by Bob Martin, and one of my issues with Martin's style is that he breaks his code into pieces which are far too small. He likes to write methods which are only 2 or 3 lines. Clearly this feels to him like the natural size for a "single responsibility," but to me, he's splitting up highly cohesive code into a profusion of tightly-coupled micro-functions, and I find the result barely legible. This, more than anything, has convinced me that the "one responsibility per function" guideline is just too flexible to be useful on its own.

> I have made many pieces of this system better by evolving a clusterfuck of cohesion into a system that is easy to reason about, maintain and evolve - by apply SOLID principles.

SOLID can describe good code (at least to some degree), but I don't think it's very good at at explaining how to improve code. You're an experienced engineer with good intuition: You see a piece of bad code, you know in your gut what it needs to be changed, and then the final product ends up being SOLID. But if you show the bad code to a junior developer and tell them only, "make it SOLID," I think they'll be lost. They don't have the instincts that tell them whether it's more appropriate to describe a method as having one responsibility or two, or whether they've split things up too much.

> In that sense I could agree open-closed principal is moot, but it’s moot for taking its argument to the logical conclusion.

I'm not sure I follow. My point is that, rather than writing `Foo` in such a way that it's easy to write `FooWithLogging extends Foo`, it should be written in such a way that it's simple to throw away the original `Foo` and write a new version that has logging built in. If you're always extending Foo through inheritance, you end up with a whole string of Foo variants, each piling new features haphazardly upon its parent. Conversely, if you're willing to go back to the drawing board for every new feature, you can evolve a sensible architecture to accommodate any number of features. Of course, if your library has a lot of customers with highly divergent needs, it might be worth building for extensibility, but I don't think that should be the default strategy.

> The most pain has been caused by previous iterations ignoring the open-closed principle.

How so? Injecting new features without adjusting the surrounding architecture to accommodate them naturally? I agree that's a problem, but I feel like extension-by-default also leads to this, since it doesn't give you the opportunity to adjust the original implementation.