> (...) so we eventually end up with two divergent implementations of Widgetness.
That means they were never the same implementation to begin with.
> Then somewhat later, Jolene Developer joins the project and is asked to fix an intermittent bug in CatWidget. The bug turns out to be partly due to the original Widget implementation, and partly due to Joe's changes (...)
Irrelevant. It means you have two components that are buggy. Create two big tickets and tackle them whenever the team has capacity.
How many months or years have passed since your hypothetical initial refactoring?
That's the problem with mindless fundamentalisme involving DRY. Throwing complexity around and tightly coupling components that in practice have no relationship other than superficial similarities are mistakes that only create technical debt in the long run.
> As far as I know there is again no hard and fast rule - it's a heuristic that we develop over the course of our careers: "is this likely to change in the future, and in what ways?"
That's the mistake you're making: fooling yourself into believing that mindlessly throwing complexity around prevents issues and future proofs implementations. It doesn't. You are only increasing the burden to maintain a project without bringing in any tangible positive tradeoff. YAGNI is a battle-tested guideline, which you're violating. Even in your example the introduction of a new widget meant bugs affecting it were either only affecting it, thus lower in risk profile, or already in the original component, which means low risk as no one stopped them. Why are you choosing to make your life harder?
OK, well, all I will add is that I've been where you are on this now, and I've also been an architecture astronaut, and now I'm somewhere in the middle. This is where I've landed based on long experience. I know which code I prefer to maintain, and it's not the big ball of mud that, in my experience, inevitably results from insufficiently abstracted designs. I'm absolutely not making my life harder; I'm making life much easier for my future self, and this is borne out by actual experience on projects I've worked on.
However as I said, there's no hard and fast rules, so, you do you.
That means they were never the same implementation to begin with.
> Then somewhat later, Jolene Developer joins the project and is asked to fix an intermittent bug in CatWidget. The bug turns out to be partly due to the original Widget implementation, and partly due to Joe's changes (...)
Irrelevant. It means you have two components that are buggy. Create two big tickets and tackle them whenever the team has capacity.
How many months or years have passed since your hypothetical initial refactoring?
That's the problem with mindless fundamentalisme involving DRY. Throwing complexity around and tightly coupling components that in practice have no relationship other than superficial similarities are mistakes that only create technical debt in the long run.
> As far as I know there is again no hard and fast rule - it's a heuristic that we develop over the course of our careers: "is this likely to change in the future, and in what ways?"
That's the mistake you're making: fooling yourself into believing that mindlessly throwing complexity around prevents issues and future proofs implementations. It doesn't. You are only increasing the burden to maintain a project without bringing in any tangible positive tradeoff. YAGNI is a battle-tested guideline, which you're violating. Even in your example the introduction of a new widget meant bugs affecting it were either only affecting it, thus lower in risk profile, or already in the original component, which means low risk as no one stopped them. Why are you choosing to make your life harder?