In a mature codebase, a code smell is a visible clue that source code may hide a deeper design problem. It is not a failure in itself: the program can compile, run, and return the right result while its structure makes the next change slower, riskier, or harder to explain. By learning to notice these clues, developers gain a practical way to improve code before routine coding turns into expensive repair work.
What code smells mean—and why they matter
While helping Martin Fowler write Refactoring, Kent Beck coined the term; the book first appeared in 1999. Fowler’s definition remains useful: a smell is “a surface indication that usually corresponds to a deeper problem in the system.” The wording matters. Rather than declaring the code bad, a smell invites inspection of the design.
For Fowler, smells are also “sniffable”: they are often quick to spot. More than a dozen lines of Java may prompt a closer look, but length alone does not prove a defect. In fact, some long methods are fine. Context decides whether a characteristic of the code is costly or simply appropriate.
For maintainability, that distinction matters. Although it can leave today’s behavior untouched, a smell can make tomorrow’s change spread through several files or require a new programmer to reconstruct hidden assumptions. In one study, three quarters of defects found in code review did not affect program execution, but they did affect software evolvability. Over time, such friction contributes to technical debt: effort deferred now and paid when the code must change.
How code smells differ from bugs
Unlike a smell, a bug is a manifested problem: the software behaves incorrectly, crashes, or fails a requirement. A code smell is a warning sign of poor code quality. It may live in a codebase for years without causing a visible error, although a review of research from 2007 through 2017 found that 24 smells significantly increased the likelihood of bugs. The smell is a predictor, not the bug.
Refactoring addresses the design rather than adding a feature or fixing a known behavioral failure. It is the disciplined restructuring of existing code without changing its external behavior. Fowler describes it as a controlled series of very small improvements to the design of an existing code base. Extract Method, Rename Variable, and Encapsulate Field are examples many development environments can automate.
The safety rule is simple: refactor only when tests are green. Without tests, there is no reliable way to tell whether a structural change preserved behavior. For untested code, first add tests that capture what it does now; then make one small change at a time.
Common code smells and the refactoring move that fits
When they point toward a specific question, smells are easier to use: what part of this design has too much responsibility, duplication, or coupling? Across object-oriented software, the following patterns recur and have focused refactoring moves.
| Smell | What to notice | Typical refactoring |
|---|---|---|
| Long Method | A function must be read top to bottom to understand one task. | Extract Function or decompose a conditional. |
| Large Class | One class accumulates fields and many responsibilities. | Extract Class. |
| Duplicated Code | The same structure appears in more than one location. | Extract Function; first slide statements together if necessary. |
| Feature Envy | A method uses another class’s data more than its own. | Move Function, sometimes after Extract Function. |
| Shotgun Surgery | One change requires small edits in many classes. | Move Function, Move Field, or combine functions into a class. |
| Primitive Obsession | Money, ranges, or phone numbers are represented only as numbers or strings. | Replace Primitive with Object. |
| Long Parameter List | A method takes many parameters or a flag argument. | Introduce Parameter Object, Preserve Whole Object, or Remove Flag Argument. |
| Data Clumps | The same group of values travels together repeatedly. | Extract Class or Introduce Parameter Object. |
Repeated switches, speculative generality, and mysterious names are also common code smells. Repeated switch blocks can yield to polymorphism. Hooks or parameters created for requirements that never arrive may call for Collapse Hierarchy, Inline Function, or removing dead code. A name that conceals a function’s purpose calls for renaming, not a comment that forces readers to translate it.
See duplicate code before it multiplies
Beyond copied text, duplicate code can appear when two branches express the same business logic with different variable names or formatting. The useful question is whether a future rule change must be made twice. If the answer is yes, duplication is likely creating an inconsistent-change risk.
When different parts of the code share a structure that can be extracted into one calculation, that calculation should be given a name that reveals its role. Do not merge code merely because two lines look alike; duplication is worth removing when the pieces change for the same reason.
In a developer survey, duplicated code was the greatest concern, at almost 20 points on the survey scale—twice the score of Long Method, the next concern. That attention is understandable: copied logic makes each later edit a coordination task.
Find smells with metrics, tools, and design knowledge
Begin with signals that are easy to count: long functions, long parameter lists, deep nesting, duplicated blocks, and unused code. Linters and static analysis can detect these reliably. For Java, PMD has existed since 2002 and provides more than 400 built-in rules across 18 languages. Across more than 40 languages, SonarQube performs static code analysis and aims for no false positives in its code-smell rules. ESLint rules illustrate useful thresholds: 50 lines for a function, three parameters, complexity of 20, and nesting depth of four are its defaults.
Rather than permission to rewrite code automatically, a metric is a prompt for inspection. From a count alone, Feature Envy or Divergent Change cannot be established; someone who understands the surrounding design must judge where the responsibility belongs. Static analysis narrows the search. Context comes from a thoughtful code review.
- Scan changed code first, where the design is already under review.
- Ask whether a change crosses more classes than the underlying business rule should require.
- Look for values that repeatedly travel together and names that conceal intent.
- Use tool findings to inspect a small area, then decide whether the smell is real in context.
Refactor in small, testable steps
Once a smell is worth addressing, resist the urge to redesign everything around it. By keeping the behavior checkable, a small refactoring also makes a failed test easier to diagnose. The following sequence protects both the code and the schedule:
- State the behavior that must not change and make sure tests are green.
- Name the smell precisely—for example, Long Method rather than “messy code.”
- Choose the smallest fitting move, such as extracting one cohesive block.
- Run the tests after that move, then commit or otherwise preserve the safe state.
- Inspect the next smell separately instead of allowing one cleanup to become an unbounded rewrite.
Although many coding environments automate mechanical parts of refactoring, automation does not supply design judgment. With tests, preserve behavior; then review the resulting interface and make sure the new names explain why the code exists.
Fix smells when change makes the cost visible
Not every smell deserves immediate cleanup. Fowler emphasizes that a smell is a reason to look, and sometimes the correct decision is to leave it alone. As an example, Fowler and Beck describe Refused Bequest as a smell often too faint to be worth cleaning: nine times out of ten, it may not justify the work.
A practical trigger is change. The camp-site rule says to leave code better than you found it, but applies especially to code already being modified. If a new feature exposes a confusing name, a data clump, or a duplicated rule, improve that nearby area while tests protect it. Record wider issues for later rather than falling into a refactoring rabbit hole.
Common myths that hide design problems
“Every smell must be removed”
No. A code smell is an indicator, not an error message. A long method may be clear and stable; a cleanup that adds indirection can make it harder to follow.
“Comments are always a smell”
No. Fowler and Beck call comments a “sweet smell.” The problem is a comment used as deodorant for code whose names and structure should explain it.
“An AI can safely refactor the whole codebase”
Not without human review and tests. A 2026 study comparing ChatGPT, Gemini, and Codeium reported ChatGPT scores of 59% on small code datasets and 77.2% on large codebases; the study found Gemini best overall for most refactoring quality attributes. A 2025 literature review concluded that LLM-refactored code is not reliable. Treat an AI suggestion as a draft to inspect, test, and revise—not evidence that behavior was preserved.
“Clean formatting means clean design”
Formatting can improve readability, but it does not remove coupling, duplicated behavior, or misplaced responsibilities. Design changes require a reasoned look at the code’s collaborators and change patterns.
Frequently Asked Questions
What does the term “code smells” mean?
It means observable signs that may point to a deeper design problem. A smell does not necessarily stop the program from working, but it can signal work that will become harder to change or maintain.
What are common examples of code smells?
Long Method, Large Class, Duplicated Code, Feature Envy, Shotgun Surgery, Primitive Obsession, Long Parameter List, and Data Clumps are widely recognized examples. Each describes a different structural concern, so each calls for a different refactoring move.
How can you identify code smells in a codebase?
Use static analysis and linters for countable signals such as long methods, deep nesting, duplicate blocks, and unused code. Then inspect the design yourself for concerns such as Feature Envy or changes that must be repeated across classes.
Is ChatGPT good at refactoring code?
It can propose useful changes, but it is not reliable enough to refactor without review. Keep tests green, examine every change, and verify that the external behavior remains the same.
How do I say “I love you” in programming code?
In C, a simple example is printf("I love you\n");. The number 143 is also used as a shorthand: one letter in “I,” four in “love,” and three in “you.” It is a programming fun fact, not a code smell.

