Skip to content

Code Smells

A code smell is a surface indication in the code that usually corresponds to a deeper problem. Smells are not bugs — the code compiles, the tests pass, the feature works — but they signal that something about the design will make the code harder to read, change, or extend. When you spot one, treat it as a prompt to look closer and, most of the time, to refactor.

The lists below are consolidated from two sources, and credit belongs to them:

  • Martin Fowler, Refactoring: Improving the Design of Existing Code (2nd ed.), Chapter 3.
  • Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, Chapter 17.

Refactoring (Fowler)

SmellWhat it is
Alternative Classes with Different InterfacesTwo classes do similar things but expose them through different method names/signatures.
CommentsA comment is being used to explain bad code instead of making the code explain itself.
Data ClassA class that is nothing but fields and getters/setters, with no behavior of its own.
Data ClumpsThe same group of data items appears together in many places (parameters, fields) and should be a single object.
Divergent ChangeOne class is changed in different ways for different reasons (mixing unrelated responsibilities).
Duplicated CodeThe same or near-identical code structure exists in more than one place.
Feature EnvyA method is more interested in the data of another class than its own.
Global DataMutable data reachable from anywhere in the program, making change unsafe.
Insider TradingModules swap data or reach into each other's internals too freely, creating tight coupling.
Large ClassA class trying to do too much, with too many fields and methods.
Lazy ElementA class, function, or layer that isn't pulling its weight and can be inlined away.
Long FunctionA function so long that its intent can't be understood at a glance.
Long Parameter ListA function takes so many parameters that callers struggle to use it and readers struggle to understand it.
LoopsAn explicit loop where a pipeline (map/filter/reduce) would state intent more clearly.
Message ChainsA client walks a long chain of objects (a.b().c().d()), coupling itself to the structure of the whole graph.
Middle ManA class that delegates most of its methods to another class and adds nothing.
Mutable DataData that can be changed in ways that cause surprising action at a distance.
Mysterious NameA name that doesn't communicate what the thing does or represents.
Primitive ObsessionUsing primitives (strings, ints) to represent domain concepts that deserve their own type.
Refused BequestA subclass inherits methods/data it doesn't want or use, signalling a broken hierarchy.
Repeated SwitchesThe same switch/if-chain on a type code appears in multiple places — polymorphism is missing.
Shotgun SurgeryA single change forces small edits across many classes.
Speculative GeneralityAbstractions, hooks, or parameters added for use cases that never materialized.
Temporary FieldA field that is only set/used under specific circumstances and is otherwise dead.

Clean Code (Martin)

Comments

CodeWhat it is
C1: Inappropriate InformationComments holding info that belongs in source control, issue trackers, or other systems.
C2: Obsolete CommentA comment that no longer matches the code it describes.
C3: Redundant CommentA comment that repeats what the code already says clearly.
C4: Poorly Written CommentA comment worth writing is worth writing well — rambling, grammatically poor, or unclear ones don't count.
C5: Commented-Out CodeDead code left behind as a comment, rotting while the rest of the codebase moves on.

Environment

CodeWhat it is
E1: Build Requires More Than One StepThe build should be a single trivial command — no multi-step dances.
E2: Tests Require More Than One StepRunning all tests should be a single command, not a sequence of setup steps.

Functions

CodeWhat it is
F1: Too Many ArgumentsZero is best, then one, then two; three is questionable; more should be avoided.
F2: Output ArgumentsParameters used to return values — counterintuitive and better replaced with a return value or method on the object.
F3: Flag ArgumentsA boolean argument telling the function which of two things to do — it's doing two things, split it.
F4: Dead FunctionA function that is never called. Delete it.

General

CodeWhat it is
G1: Multiple Languages in One Source FileOne file mixing Java, HTML, JS, SQL, XML, etc. — keep each file to one language where possible.
G2: Obvious Behavior Is UnimplementedUsers of a function reasonably expect certain behavior (e.g. dayOfWeek("Monday")); violating that principle of least surprise breaks trust.
G3: Incorrect Behavior at the BoundariesEdge cases aren't handled; the author relied on intuition instead of writing tests to prove behavior.
G4: Overridden SafetiesDisabling compiler warnings, failing tests, or @SuppressWarnings instead of fixing the underlying problem.
G5: DuplicationEvery duplication is a missed abstraction opportunity — applies to literal code, parallel switch statements, and similar algorithms.
G6: Code at Wrong Level of AbstractionLow-level details mixed into a high-level policy class, or vice versa.
G7: Base Classes Depending on Their DerivativesBase classes that know about specific subclasses — a design inversion that couples abstractions to concretions.
G8: Too Much InformationWell-defined modules have small interfaces; leaking lots of internals via public methods creates coupling.
G9: Dead CodeUnreachable branches, unused utilities, unthrown exceptions — delete it.
G10: Vertical SeparationVariables and helper functions should be defined close to where they're used, not far above.
G11: InconsistencyPick a convention and stick to it — readers assume similar-looking things behave similarly.
G12: ClutterUnused variables, empty constructors, pointless defaults — keep the file clean.
G13: Artificial CouplingConstants, enums, or utilities stashed in unrelated classes, creating dependencies for convenience.
G14: Feature EnvyA method manipulating data from another class more than its own — the method probably belongs over there.
G15: Selector ArgumentsBoolean, enum, or int arguments used to pick behavior inside a function — split into multiple functions instead.
G16: Obscured IntentDense, cryptic code that takes effort to decode — intent should be unmissable.
G17: Misplaced ResponsibilityCode placed where a reader wouldn't expect it — put it where the principle of least surprise says it should go.
G18: Inappropriate StaticA function made static when it might reasonably need polymorphic behavior later.
G19: Use Explanatory VariablesBreaking calculations into intermediate, well-named variables makes the intent legible.
G20: Function Names Should Say What They DoIf you can't tell what a function does from its name, rename it or redesign it.
G21: Understand the AlgorithmDon't stop when tests pass — know why it works. Patchwork functions are a sign you didn't.
G22: Make Logical Dependencies PhysicalIf module A depends on something in B, make that dependency explicit (pass it in) rather than assuming it.
G23: Prefer Polymorphism to If/Else or Switch/CaseMost switches on type should be replaced with polymorphic dispatch.
G24: Follow Standard ConventionsEvery team should follow a shared coding standard; deviating without reason creates friction.
G25: Replace Magic Numbers with Named ConstantsRaw literals (or magic strings) scattered through code lose their meaning.
G26: Be PreciseDon't guess — check for nulls, handle concurrency, use the right type, round deliberately.
G27: Structure over ConventionEnforce design decisions with structure (abstract methods) rather than conventions (naming).
G28: Encapsulate ConditionalsExtract complex boolean expressions into well-named functions.
G29: Avoid Negative Conditionalsif (!buffer.shouldNotCompact()) — flip the logic so positives read naturally.
G30: Functions Should Do One ThingA function with several sections each doing different work should be split.
G31: Hidden Temporal CouplingsFunction A must be called before B, but nothing in the signatures enforces it — make the order explicit.
G32: Don't Be ArbitraryStructure your code for a reason; arbitrary structure invites others to change it arbitrarily.
G33: Encapsulate Boundary Conditionslevel + 1 appearing all over is a boundary — capture it in a well-named variable.
G34: Functions Should Descend Only One Level of AbstractionStatements inside a function should all sit one level below the function's own name.
G35: Keep Configurable Data at High LevelsConstants and config live near the top of the call stack, not buried in low-level utilities.
G36: Avoid Transitive NavigationDon't chain through collaborators' collaborators (Law of Demeter) — depend on what you directly use.

Names

CodeWhat it is
N1: Choose Descriptive NamesNames set 90% of a reader's expectations — take the time to pick well and rename freely.
N2: Choose Names at the Appropriate Level of AbstractionName things by what they mean at the current level, not by implementation details.
N3: Use Standard Nomenclature Where PossibleReuse terms from patterns, conventions, or your domain's ubiquitous language.
N4: Unambiguous NamesPick names that leave no doubt about what the function or variable does.
N5: Use Long Names for Long ScopesShort scopes can afford short names; long scopes need longer, more descriptive names.
N6: Avoid EncodingsDon't prefix types, scopes, or Hungarian notation onto names — modern tooling makes it noise.
N7: Names Should Describe Side-EffectsIf a getter also creates or mutates, the name must reveal that.

Tests

CodeWhat it is
T1: Insufficient TestsA test suite should cover everything that could reasonably break.
T2: Use a Coverage ToolCoverage tools reveal gaps your intuition missed.
T3: Don't Skip Trivial TestsTrivial tests are cheap and their documentary value is high.
T4: An Ignored Test Is a Question about an AmbiguityIf you can't decide whether a behavior is right, annotate the test and flag the ambiguity — don't silently drop it.
T5: Test Boundary ConditionsSpecial attention to the middle is easy; bugs hide at the boundaries.
T6: Exhaustively Test Near BugsBugs cluster. When you find one, look harder at the function around it.
T7: Patterns of Failure Are RevealingWhich tests fail can diagnose the problem faster than reading a stack trace.
T8: Test Coverage Patterns Can Be RevealingLooking at what is not executed by passing tests exposes why failing tests fail.
T9: Tests Should Be FastSlow tests get skipped; skipped tests miss bugs. Keep them fast.

Java

CodeWhat it is
J1: Avoid Long Import Lists by Using WildcardsWildcard imports keep imports short and avoid coupling to every specific name in a package.
J2: Don't Inherit ConstantsUsing inheritance just to import constants hides their source — use static imports instead.
J3: Constants versus EnumsPrefer enum over public static final int for typed, meaningful constant sets.