I have been in discussions about how far to refactor a lot of times. “Until duplication is removed”, is a common position, and sure, that is fine.
But what is really great is to refactor towards abstraction, until you cannot abstract more and still solve the problem. This way you end up with the most general code that solves the current problems, which put the least possible constraint on future development.
This is not the same as writing code in speculation. If you need to speculate to abstract more, you are done.
It is about using the tools we have, our programming languages, more in the way our brains work: We experience specific scenarios, abstract the essence from them, and store that essence. Later we can apply that essence on similar or totally different scenarios and problems. The same with abstract code.
Abstracted, problem solving code is the essence of our programs, and we should strive to make it as pure as possible.