* every abstraction has a cost - DO a quick cost/benefit analysis in your head, or even better in the code comments, when choosing for/against an abstraction.
* generally, start with KISS. - a process begins with a verb and its design usually starts with being a function/procedure - pieces of data that belong together across multiple processes should start out as an immutable object or struct. In python I’m almost never using direct descendants of object, rather I inherit either from NamedTuple (for final classes) or I decorate with @dataclass(frozen=True) for non-final.
* using the above, you’ll start seeing violations of DRY. DO use the rule-of-threes. If there’s a second repetition you’re either writing, or already anticipate based on the JIRA backlog - factor out the repeating code using an appropriate abstraction.
I’ve found that code written with these rules tends to be simple yet clean (these two properties can often be opposite, I’ve seen simple code that is constantly repeating itself and thus becomes unmaintainable, and I’ve also seen clean code that is the opposite of simple because of an abundance of abstractions (think FizzBuzzEnterprise).