Extract to Name, Not to Dedupe
Ask most engineers why they extracted a function and you get one answer: the logic was duplicated, so it moved to one place. DRY. It's the only justification that survives review without an argument, because it makes itself: here are the two call sites, here is the one definition, done.
There's a second reason to extract that almost nobody writes down, and it has nothing to do with repetition. You pull code out to put a word into the codebase.
The single-use extraction
The code appears once. Read inline, it's clear enough. Someone extracts it anyway, gives it a name, and the file is now longer by a signature and a return. The reflex on review is to flatten it back: this runs in exactly one place, why is it a function?
Usually that reflex is wrong, and the reason it's wrong is that the extraction never had anything to do with reuse. The function body is just where the name lives. The name is the deliverable.
Take a billing check. Inline, you have now - subscription.canceledAt < GRACE_PERIOD_MS. Pulled out, you have isWithinGracePeriod(subscription). The arithmetic didn't get clearer; it got hidden. What changed is that "grace period" is now a thing the code says out loud. It runs once today. It's still worth extracting, because the next person who needs to know how the system treats a just-canceled subscription has a word to grep for instead of a date subtraction to recognize.
Names leak into how the team talks
This is the part the readability argument undersells. Shorter call sites and less to hold in your head are real, but they're the local payoff. The durable one is that the words accumulate.
A codebase where the concepts are named is one where people can talk about what the system does without reaching back into the implementation every time. Someone says "we don't bill inside the grace period" in a standup and everyone maps it to isWithinGracePeriod without thinking. The vocabulary the code uses becomes the vocabulary the team uses. Design docs borrow it. Onboarding gets faster because the new hire learns the same forty words the code already knows, instead of forty words in the wiki that drifted from forty other words in the source.
You don't get that from inline logic, however clear. A correct expression that no one can name is a fact the system knows and the team can't discuss.
The test, and the failure mode
The test I use: would this name show up in a design doc? Would someone explaining the system to a colleague reach for it without translating? If yes, extract it. The concept already exists in how people think about the domain; the code should hold the same word.
If the best name you can find is doTheCheckAndReturn or handleSubscription2, the concept isn't real yet. You're not naming anything. You're chopping a function into paragraphs and pretending the line breaks are meaning. That's the inverse failure, and extracting freely produces a lot of it: a codebase full of almost-words for things that aren't concepts, a vocabulary that bloats faster than it clarifies. New contributors dutifully learn the fake words and then have to unlearn them. The discipline isn't "extract more." It's "extract when there's a real word on the other side."
Why it's hard to defend
All of this bites in review, because the value is invisible in the diff. DRY shows its work: point at the duplication, the argument is over. Vocabulary extraction asks the reviewer to believe in a payoff that lands somewhere else, somewhere later. The name matters when someone searches for it in six months, when it turns up in a postmortem, when a new hire says "oh, so isWithinGracePeriod is where that rule lives" and gets it right the first time. None of those moments are on the screen during review.
So the single-use extraction looks like overhead and gets flagged as overhead. Sometimes it is. But before you inline it back, check whether you'd be deleting a line of code or deleting a word the project was going to need. Those are not the same edit, and only one of them is free.