I would add that sometimes leaving oneself room to expand/respond to changes is just what your code needs. Expansion points. Whatever you want to call it.
The maxim I use is, "avoid being overly specific." If you have polymorphism in your language the less you say about the type of a variable the more places it can be used and the more ways it can be composed with other functions. This requires a style of design that pushes side effects to the edges of the program (consequently where they're the easiest to change).
With this style of programming responding to change is straight-forward to reason about. No need for complicated indirection between objects and tracing behaviors through v-tables. If you are using OOP keep your data and behaviors separate.
The stuff that solidifies rarely changes. The stuff built on it changes a lot. And as time goes on you'll find that refactors will start pushing more upper layers down where they will eventually solidify.
I would add that sometimes leaving oneself room to expand/respond to changes is just what your code needs. Expansion points. Whatever you want to call it.
The maxim I use is, "avoid being overly specific." If you have polymorphism in your language the less you say about the type of a variable the more places it can be used and the more ways it can be composed with other functions. This requires a style of design that pushes side effects to the edges of the program (consequently where they're the easiest to change).
With this style of programming responding to change is straight-forward to reason about. No need for complicated indirection between objects and tracing behaviors through v-tables. If you are using OOP keep your data and behaviors separate.
The stuff that solidifies rarely changes. The stuff built on it changes a lot. And as time goes on you'll find that refactors will start pushing more upper layers down where they will eventually solidify.