I'm looking for guidance about DRY vs Code coupling. I do not like to duplicate my code and I also do not like code coupling between unrelated modules. So I refactor duplicate code if I find identically duplicate code a year after the duplication was introduced. However, I have increasingly experienced situations where the real world is much more unpredictable, and after refactoring the code, there arise situations which require forking out the code again.
For example, if I had code to handle gasoline cars, gasoline SUVs, electric cars and electric SUVs, lets say I refactored duplicate code into the "gasoline" hierarchy and the "electric" hierarchy, both descending from the "vehicle" hierarchy. So far so good. And then, my company introduces a hybrid car and a hybrid Semi - that would require core changes to my original hierarchy itself. Maybe it would require "composition" between the gasoline and the electric hierarchies.
Clearly code duplication is bad because it increases the time taken to implement a change common to all the above products. But refactoring common code makes it equally hard to introduce product-specific variations, and leads to a lot of "class-jumping" when one has to find the line of code to fix a bug - one change in a higher-level parent class could trigger trigger regression bugs among all descendants.
How does one strike an optimal balance between DRY vs unwanted code coupling?