When we think about the knowledge of "how to do something" that's where things can get confusing. 9/10 times I'd say that right move is to look for common assumptions or facts. IE it isn't just "doing something" that is important, but the assumptions made in the process of doing it:
As an example, consider finding the average word length in some piece of text. We might start writing that feature like:
def count_words(text: str) -> int:
return len(text.split(' '))
def average_word_length(text: str) -> int:
num_words = count_words(text)
word_lengths = []
for word in text.split(' '):
word_lengths.append(len(word))
return sum(word_lengths) / num_words
then the piece of knowledge they share is "what a word is" and the DRY refactoring would pull out that piece of knowledge into its own function def words(text: str) -> List[str]:
return text.split(' ')
that might be code you write when starting to write a feature and that's the kind of "ding ding ding there's common knowledge here" that should guide refactoring. The system has a concept of a "word" that we've introduced and its important that knowledge about "what a word is" in one place. For DRY things it frequently doesn't make any sense for there to be multiple statements of "what a word is" where the system wants to use the same concept.Kind of orthogonal to this is abstraction where the focus is on "usefulness" and that is where 100% you can abstract incorrectly, prematurely, get screwed over by requirement changes, write a library that hides everything and makes people angry. The example you provide seems more like an error in abstraction where things that should be close together are too far apart in the system (ie, some "fact" is hidden away and another part of the system wants to know it), but the consolidation and DRYing of those facts, I'd argue, is a lot easier once we've figured out how to identify them