I feel like the balance has shifted over the last 30 years, and is speeding up. Semi-automatic and fully automatic re-factoring has made dealing with duplicated code much faster, cheaper and safer. Changing abstraction is still high risk.

Isn't it the opposite?

Automated re-factoring means you can refactor duplicated code only as long as it is exactly duplicate.

Whereas the whole problem is that when somebody changes 3 out of 10 of the duplicate cases in a simple way that they are no longer exactly duplicate, and then somebody fixes a bug in one of the other 7/10 cases, they can update the bug across the 7 "duplicate" cases but they'll miss the 3 that aren't.

The problem with duplicate code is always when some of the instances get changed/fixed but not all of them. And that when somebody edits one instance, they often aren't even aware of all the other instances.

Abstractions are low-risk, because you know where the code is. If it's the wrong abstraction, you can fix that and know what you're fixing. Whereas with duplicated-yet-modified code, you've now lost the connections between them.

    > Automated re-factoring means you can refactor duplicated code only as long as it is exactly duplicate.
This has not been true in JetBrains' IntelliJ for more than 10 years. It can parameterize refactoring multiple blocks of code.

I have regularly watched agents forget to update one duplicated pattern after changing it somewhere else. If it's within a single file or related class, it'll catch it, but if it's off in some other package in the monorepo, it's a crapshoot.

Changing abstraction is a high risk unlike agents refactoring scores of almost identical code.

I thought this discussion was limited to situations where you care about code quality