For me the most helpful general guideline has been.
Make the code easy to change.
I can break rules like DRY, if the repeating myself makes it easy to change.
I can break rules like DRY, if the repeating myself makes it easy to change.
Do that for a couple of years in a single codebase and the beautiful abstraction design has changed into a state machine with a transition between almost every pair of states