←back to thread

361 points mmphosis | 6 comments | | HN request time: 0.844s | source | bottom
1. vander_elst ◴[] No.42165926[source]
> It's better to have some wonky parameterization than it is to have multiple implementations of nearly the same thing. Improving the parameters will be easier than to consolidate four different implementations if this situation comes up again.

From https://go-proverbs.github.io/: A little copying is better than a little dependency.

Curious to see how the community is divided on this, I think I'm more leaning towards the single implementation side.

replies(4): >>42165963 #>>42166107 #>>42166264 #>>42169200 #
2. OtomotO ◴[] No.42165963[source]
I decide on a case by case basis.

I've been bitten by both decisions in the past. Prematurely abstracting and "what's 4 copies gonna do, that's totally manageable" until it cost quite some time to fix bugs (multiple times then, and because of diverged code paths, with multiple different solutions)

replies(1): >>42166207 #
3. abound ◴[] No.42166107[source]
Like most things, blanket advice will break down in some cases, things can be highly contextual.

Generally, my anecdotal experience is that Go libraries have far fewer average dependencies than the equivalent Rust or JavaScript libraries, and it may be due in part to this (the comprehensive standard library also definitely helps).

I definitely tend to copy small snippets between my projects and rely sparingly on dependencies unless they're a core part of the application (database adapter, heavy or security-sensitive specifications like OIDC, etc)

4. ulbu ◴[] No.42166207[source]
I think an abstraction should imply/enforce a common abstract structure. It inscribes an abstraction layer into the system. Moving a couple of concrete lines into a single named scope is orthogonal to this.
5. horsawlarway ◴[] No.42166264[source]
The older I get, and the more experience I have, the more I think "single implementation" is generally a lie we tell to ourselves.

To the author's point - a wonky param to control code flow is a clear and glaring sign that you consolidated something that wasn't actually the same.

The similarity was a lie. A mistake you made because young features often have superficial code paths that look similar, but turn out to be critically distinct as your product ages.

Especially with modern type systems - go ahead and copy, copy twice, three times, sometimes more. It's so much easier to consolidate later than it is to untangle code that shouldn't have ever been intertwined in the first place. Lean on a set of shared types, instead of a shared implementation.

My future self is always happier with past me when I made a new required changeset tedious but simple. Complexity is where the demons live, and shared code is pure complexity. I have to manage every downstream consumer, get it right for all of them, and keep it all in my head at the same time. That starts off real easy at shared consumer number 2, and is a miserable, miserable experience by consumer number 10, with 6 wonky params thrown in, and complex mature features.

---

So for me - his rule of thumb is egregiously too strict. Consolidate late and rarely. Assume the similarity is a lie.

6. brigandish ◴[] No.42169200[source]
I was going to disagree with this because I thought "but what about the tests!", but in the linked video of Rob Pike's talk he says (paraphrased) "but then of course there's a test, so that every time it is tested, it guarantees that the library and the copied code agree on their definition. The test has a library dependency but the copied code doesn't".

That's actually a really clever way to do things and I think I'll adopt it.