I've been making a conscious effort to restrain my tendency to over-engineer, and part of that involves internalizing Donald Knuth's famous quote, "premature optimization is the root of all evil". Which I think extends to abstractions.
I have this desire for my code to reach a state where I can think in terms of my higher-level abstractions, and not expend cognitive exertion in mentally computing the (relatively) lower-level details of my code.
However, I am realizing more and more that eagerly building your abstractions can pigeon hole you into this undesirable state where your abstractions don't accurately reflect your needs anymore, and the work to tear down these abstractions and refine them is far too great.
This is because you prematurely abstracted, and didn't have enough clarity on the problem to accurately design your abstractions, coupled with the fact that your eagerness caused you to continue building and committing to them.
This story is all too similar to that of premature optimization: most of the issues arise from trying to eagerly solve a problem before it fully exists in reality.
My new approach is that I am trying to only form and commit to abstractions when it solves a pressing issue in my code. I've been employing this in building glyph. Organizationally I have been just keeping things simple, making higher-level abstractions only when I feel a need for them.
The exception of course is when the commitment is highly localized and doesn't permeate the other abstraction barriers in my code, meaning the change only affects a small surface area of my code and can easily be reverted or swapped for something else.
The true beauty of this approach is that when the time comes for an abstraction, it is extremely easy to analyze and reason about my code to determine what I need. The code just exists in a simpler, purer form without the logic being muddied by layers of abstractions which feel like indirections that distract you.
This is also a common problem. When people write verbose code I think its sometimes a mistake to immediately attempt to reduce that verbosity with abstractions.
Because sometimes your code is verbose because the logic is actually complex, and if you try to alleviate the verbosity too prematurely... well you've made it look pretty but you've just made the code 10x harder to understand because that complex logic is now spread through multiple layers of indirection and multiple barriers of abstraction. Sometimes it's okay to have isolated areas of extreme verbosity.