And I am only half-joking about that. I don't think that effort is that visible and often goes unrewarded. I feel like a lot of managers don't directly, but indirectly use number of tickets closed as a sign of productivity which affects promotions and compensation.
Obviously YMMV, but teams that care about their code quality to such an extent are less likely than places that act as ticket factories.
The approach we've found that works is health checks and manually looking into cases when we think we've fixed a bug, as it will often point us to a piece of duplicated code we missed that we can wrap into the fold.
Duplication is easier because once you fix that single place - you are 100% sure you fixed that place and you did not break 10 other places. Maybe you know "by writing unit tests", but when you write unit tests, when you find out you broke something.
Funny story time: had an add/edit popup in system because they looked the same so dev just made it "single thing". Something like 3 months dev1 fixed something -> qa2 found a bug X -> dev2 fixed something -> qa1 found a bug Y -> dev3 fixed something -> qa 3 again found bug X. When I got into code base I noticed that ping-pong because somehow I was only sane person to check git history and I split things up. Something like that happened multiple times in my career
- write stuff as fast as possible, without having time to think about overall architecture, especially if it involves having to cooperate with other devs. It's easier to just implement something that's as quick and as simple as possible so that it can be passed off to someone else with minimum effort.
- no need to communicate the abstraction semantics - no need for documentation outlining the abstraction, reasoning, possible expansion, etc.
- it's much easier to make localized changes. A well written abstraction will cover some logic that might be spread across multiple areas. Changing something major to the abstraction requires understanding how the abstraction affects all it's applications in the same ticket. Whereas duplicated code can result in a ticket being resolved by just making a change to a specific code block, like a function.
- Things that work well aren't appreciated. If it's easy to update an abstraction to a new feature, it'll be the expected outcome. When a change like the previous point is needed, it's much more memorable because of the frustrating experience is likely to be longer and more strenuous. We also tend to remember negative experiences over positive ones.
- Abstractions require reading more code with additional levels of indirection and devs don't like reading other people's code.
- Writing things well requires effort, so bad abstractions are more likely.
- More mature projects tend to have more abstractions because of their additional complexity, so I would guess that there's a strong correlation between difficult projects and frequency of abstractions.
- Some people went absolutely nuts with writing blogs posts, and evangelizing certain techniques which were completely unnecessary in an effort to push out content. There's lots of things to write about on implementing abstractions. But little in the other direction other than don't write unnecessary abstractions.