Appearance
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)
| Smell | What it is |
|---|---|
| Alternative Classes with Different Interfaces | Two classes do similar things but expose them through different method names/signatures. |
| Comments | A comment is being used to explain bad code instead of making the code explain itself. |
| Data Class | A class that is nothing but fields and getters/setters, with no behavior of its own. |
| Data Clumps | The same group of data items appears together in many places (parameters, fields) and should be a single object. |
| Divergent Change | One class is changed in different ways for different reasons (mixing unrelated responsibilities). |
| Duplicated Code | The same or near-identical code structure exists in more than one place. |
| Feature Envy | A method is more interested in the data of another class than its own. |
| Global Data | Mutable data reachable from anywhere in the program, making change unsafe. |
| Insider Trading | Modules swap data or reach into each other's internals too freely, creating tight coupling. |
| Large Class | A class trying to do too much, with too many fields and methods. |
| Lazy Element | A class, function, or layer that isn't pulling its weight and can be inlined away. |
| Long Function | A function so long that its intent can't be understood at a glance. |
| Long Parameter List | A function takes so many parameters that callers struggle to use it and readers struggle to understand it. |
| Loops | An explicit loop where a pipeline (map/filter/reduce) would state intent more clearly. |
| Message Chains | A client walks a long chain of objects (a.b().c().d()), coupling itself to the structure of the whole graph. |
| Middle Man | A class that delegates most of its methods to another class and adds nothing. |
| Mutable Data | Data that can be changed in ways that cause surprising action at a distance. |
| Mysterious Name | A name that doesn't communicate what the thing does or represents. |
| Primitive Obsession | Using primitives (strings, ints) to represent domain concepts that deserve their own type. |
| Refused Bequest | A subclass inherits methods/data it doesn't want or use, signalling a broken hierarchy. |
| Repeated Switches | The same switch/if-chain on a type code appears in multiple places — polymorphism is missing. |
| Shotgun Surgery | A single change forces small edits across many classes. |
| Speculative Generality | Abstractions, hooks, or parameters added for use cases that never materialized. |
| Temporary Field | A field that is only set/used under specific circumstances and is otherwise dead. |
Clean Code (Martin)
Comments
| Code | What it is |
|---|---|
| C1: Inappropriate Information | Comments holding info that belongs in source control, issue trackers, or other systems. |
| C2: Obsolete Comment | A comment that no longer matches the code it describes. |
| C3: Redundant Comment | A comment that repeats what the code already says clearly. |
| C4: Poorly Written Comment | A comment worth writing is worth writing well — rambling, grammatically poor, or unclear ones don't count. |
| C5: Commented-Out Code | Dead code left behind as a comment, rotting while the rest of the codebase moves on. |
Environment
| Code | What it is |
|---|---|
| E1: Build Requires More Than One Step | The build should be a single trivial command — no multi-step dances. |
| E2: Tests Require More Than One Step | Running all tests should be a single command, not a sequence of setup steps. |
Functions
| Code | What it is |
|---|---|
| F1: Too Many Arguments | Zero is best, then one, then two; three is questionable; more should be avoided. |
| F2: Output Arguments | Parameters used to return values — counterintuitive and better replaced with a return value or method on the object. |
| F3: Flag Arguments | A boolean argument telling the function which of two things to do — it's doing two things, split it. |
| F4: Dead Function | A function that is never called. Delete it. |
General
| Code | What it is |
|---|---|
| G1: Multiple Languages in One Source File | One file mixing Java, HTML, JS, SQL, XML, etc. — keep each file to one language where possible. |
| G2: Obvious Behavior Is Unimplemented | Users of a function reasonably expect certain behavior (e.g. dayOfWeek("Monday")); violating that principle of least surprise breaks trust. |
| G3: Incorrect Behavior at the Boundaries | Edge cases aren't handled; the author relied on intuition instead of writing tests to prove behavior. |
| G4: Overridden Safeties | Disabling compiler warnings, failing tests, or @SuppressWarnings instead of fixing the underlying problem. |
| G5: Duplication | Every duplication is a missed abstraction opportunity — applies to literal code, parallel switch statements, and similar algorithms. |
| G6: Code at Wrong Level of Abstraction | Low-level details mixed into a high-level policy class, or vice versa. |
| G7: Base Classes Depending on Their Derivatives | Base classes that know about specific subclasses — a design inversion that couples abstractions to concretions. |
| G8: Too Much Information | Well-defined modules have small interfaces; leaking lots of internals via public methods creates coupling. |
| G9: Dead Code | Unreachable branches, unused utilities, unthrown exceptions — delete it. |
| G10: Vertical Separation | Variables and helper functions should be defined close to where they're used, not far above. |
| G11: Inconsistency | Pick a convention and stick to it — readers assume similar-looking things behave similarly. |
| G12: Clutter | Unused variables, empty constructors, pointless defaults — keep the file clean. |
| G13: Artificial Coupling | Constants, enums, or utilities stashed in unrelated classes, creating dependencies for convenience. |
| G14: Feature Envy | A method manipulating data from another class more than its own — the method probably belongs over there. |
| G15: Selector Arguments | Boolean, enum, or int arguments used to pick behavior inside a function — split into multiple functions instead. |
| G16: Obscured Intent | Dense, cryptic code that takes effort to decode — intent should be unmissable. |
| G17: Misplaced Responsibility | Code placed where a reader wouldn't expect it — put it where the principle of least surprise says it should go. |
| G18: Inappropriate Static | A function made static when it might reasonably need polymorphic behavior later. |
| G19: Use Explanatory Variables | Breaking calculations into intermediate, well-named variables makes the intent legible. |
| G20: Function Names Should Say What They Do | If you can't tell what a function does from its name, rename it or redesign it. |
| G21: Understand the Algorithm | Don't stop when tests pass — know why it works. Patchwork functions are a sign you didn't. |
| G22: Make Logical Dependencies Physical | If 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/Case | Most switches on type should be replaced with polymorphic dispatch. |
| G24: Follow Standard Conventions | Every team should follow a shared coding standard; deviating without reason creates friction. |
| G25: Replace Magic Numbers with Named Constants | Raw literals (or magic strings) scattered through code lose their meaning. |
| G26: Be Precise | Don't guess — check for nulls, handle concurrency, use the right type, round deliberately. |
| G27: Structure over Convention | Enforce design decisions with structure (abstract methods) rather than conventions (naming). |
| G28: Encapsulate Conditionals | Extract complex boolean expressions into well-named functions. |
| G29: Avoid Negative Conditionals | if (!buffer.shouldNotCompact()) — flip the logic so positives read naturally. |
| G30: Functions Should Do One Thing | A function with several sections each doing different work should be split. |
| G31: Hidden Temporal Couplings | Function A must be called before B, but nothing in the signatures enforces it — make the order explicit. |
| G32: Don't Be Arbitrary | Structure your code for a reason; arbitrary structure invites others to change it arbitrarily. |
| G33: Encapsulate Boundary Conditions | level + 1 appearing all over is a boundary — capture it in a well-named variable. |
| G34: Functions Should Descend Only One Level of Abstraction | Statements inside a function should all sit one level below the function's own name. |
| G35: Keep Configurable Data at High Levels | Constants and config live near the top of the call stack, not buried in low-level utilities. |
| G36: Avoid Transitive Navigation | Don't chain through collaborators' collaborators (Law of Demeter) — depend on what you directly use. |
Names
| Code | What it is |
|---|---|
| N1: Choose Descriptive Names | Names set 90% of a reader's expectations — take the time to pick well and rename freely. |
| N2: Choose Names at the Appropriate Level of Abstraction | Name things by what they mean at the current level, not by implementation details. |
| N3: Use Standard Nomenclature Where Possible | Reuse terms from patterns, conventions, or your domain's ubiquitous language. |
| N4: Unambiguous Names | Pick names that leave no doubt about what the function or variable does. |
| N5: Use Long Names for Long Scopes | Short scopes can afford short names; long scopes need longer, more descriptive names. |
| N6: Avoid Encodings | Don't prefix types, scopes, or Hungarian notation onto names — modern tooling makes it noise. |
| N7: Names Should Describe Side-Effects | If a getter also creates or mutates, the name must reveal that. |
Tests
| Code | What it is |
|---|---|
| T1: Insufficient Tests | A test suite should cover everything that could reasonably break. |
| T2: Use a Coverage Tool | Coverage tools reveal gaps your intuition missed. |
| T3: Don't Skip Trivial Tests | Trivial tests are cheap and their documentary value is high. |
| T4: An Ignored Test Is a Question about an Ambiguity | If you can't decide whether a behavior is right, annotate the test and flag the ambiguity — don't silently drop it. |
| T5: Test Boundary Conditions | Special attention to the middle is easy; bugs hide at the boundaries. |
| T6: Exhaustively Test Near Bugs | Bugs cluster. When you find one, look harder at the function around it. |
| T7: Patterns of Failure Are Revealing | Which tests fail can diagnose the problem faster than reading a stack trace. |
| T8: Test Coverage Patterns Can Be Revealing | Looking at what is not executed by passing tests exposes why failing tests fail. |
| T9: Tests Should Be Fast | Slow tests get skipped; skipped tests miss bugs. Keep them fast. |
Java
| Code | What it is |
|---|---|
| J1: Avoid Long Import Lists by Using Wildcards | Wildcard imports keep imports short and avoid coupling to every specific name in a package. |
| J2: Don't Inherit Constants | Using inheritance just to import constants hides their source — use static imports instead. |
| J3: Constants versus Enums | Prefer enum over public static final int for typed, meaningful constant sets. |