Cost models illustrative. Numbers from academic and industry research; methodology documented. Not investment or engineering advice; your mileage will vary.
last verified April 202612 min read

When smells are OK: four contexts where refactoring is the wrong answer

This site has a point of view. But a useful point of view needs its limits, or it becomes a slogan. The same editorial stance is taken by our sister publication, featurebloat.com, on the product side of the same argument.

The catalog on /the-22-smells is a diagnostic tool, not a moral code. Smells are signals that warrant investigation. They are not verdicts that require immediate remediation. Four contexts exist where a code smell is the correct, defensible choice.

§ 01
Context 1: Prototype Code

Prototype code exists to test a hypothesis. The engineering question is: does this approach work? Not: is this approach clean? Refactoring prototype code is a category error. The refactoring investment buys quality that is wasted if the hypothesis fails.

The risk is not prototyping itself; it is the survival of prototypes into production without revision. Prototypes succeed at a higher rate than we expect, and they accumulate users faster than we expect. The discipline is:

§ 02
Context 2: Expiring Code

Expiring code is code scheduled for deletion: regulatory migrations, end-of-life modules, feature flags scheduled for retirement, monolith extractions in progress. Refactoring expiring code is throwing capital at a depreciating asset.

The discipline: annotate expiring code, block further investment except bug fixes on the migration path, and keep a sunset calendar. The risk of untracked expiry dates is that code meant to be temporary becomes permanent by default.

// @deprecated - scheduled for deletion 2026-Q3 // Migration tracked in JIRA-4821 // DO NOT invest in refactoring. Fix bugs on the migration path only. export function legacyOrderProcessor(order: Order) { // ... smelly, but intentionally so }
§ 03
Context 3: Deadline-Bound Code

The ship date is non-negotiable. The alternative is not shipping. Smells accumulated under deadline pressure are a deliberate trade, not a failure. The argument on this site is not that smells should never be introduced; it is that unacknowledged smells compound silently.

The discipline is explicit acknowledgment: “we are introducing this smell deliberately, we know the cost mechanism, and we are booking it as technical debt to be addressed in Q[X].” The booking converts a surprise (why is this here?) into a decision record (we chose this).

Bavota 2015 found that smells introduced under commit messages indicating time pressure have higher associated defect rates than smells introduced at leisure. The debt-booking discipline does not reduce the defect rate; it reduces the time-to-remediation, which is the next best thing.

§ 04
Context 4: Cognitively-Coupled Code

Some code is smelly because the domain is smelly. Tax calculation. Regulatory compliance. Cryptographic protocols. International standards. The code is long, complex, and difficult to read because the underlying domain is long, complex, and difficult.

Refactoring the cosmetic surface of domain-essential complexity while the domain complexity remains produces a different kind of confusion. Now the code looks clean but the reader still has to hold the domain complexity in their head, and the “clean” structure gives them false confidence that it is simpler than it is.

The discipline: distinguish accidental complexity (we made it harder than it needs to be) from essential complexity (the domain requires this). Refactor aggressively for accidental. Accept and document carefully for essential.

§ 05
Ousterhout's Minority Report

John Ousterhout's A Philosophy of Software Design (Yaknyam Press, 2018; 2nd ed. 2021) is the most rigorous counterpoint to the anti-smell orthodoxy. Ousterhout argues against several of Robert C. Martin's Clean Code recommendations, specifically on function size.

Ousterhout's central claim: the key design quality is not small functions but deep modules - modules with simple interfaces that hide large amounts of implementation detail. Over-small functions produce shallow modules that expose their implementation through long chains of trivially named helpers. A reader navigating ten 5-line functions to understand one operation has a harder time than a reader navigating one 30-line function with clear naming and a comment header.

The important framing: Ousterhout is not arguing against all decomposition. He is arguing against decomposition as a goal in itself. “Methods should be short” is not the right principle. “Methods should have a single, clear responsibility at the right level of abstraction” is.

This site recommends reading both Clean Code and A Philosophy of Software Design. The field legitimately disagrees. A useful practitioner reads both and draws a considered position. See /books-and-references for honest reviews of both.

§ 06
The Anti-Dogma Test

Three questions to ask before every refactoring decision:

Will this code exist in twelve months?

If the answer is probably not, the refactoring investment may not be recovered. Expiring code does not benefit from structural improvement.

Does the smell reflect accidental or essential complexity?

Accidental: we introduced this complexity unnecessarily. Essential: the domain requires this complexity. Refactor aggressively for accidental; accept and document for essential.

Is the refactor cost recoverable from its risk-reduction value?

Run the numbers. A smell that costs $5,000/year requires a refactoring investment of $5,000 or less to have a positive ROI in year one. If the refactor costs more, the investment does not pay back in the near term.

If all three questions answer in the direction of “no” (code is expiring, smell is essential, cost exceeds near-term return), stop. Ship, acknowledge the debt, book it, move on. The catalog is a tool for finding the smells worth fighting. Not every smell is worth the fight.

The 22 SmellsRefactoring ROIOusterhout reviewfeaturebloat.com