Concept · Code design

Code smells

Surface signs — long functions, data clumps, feature envy, shotgun surgery — that hint at a deeper design problem worth refactoring.

3 minrefactoringfeature envyprimitive obsessiondata clumps

A code smell, a term coined by Kent Beck and popularised in Martin Fowler's Refactoring, is a surface feature of code that usually indicates a deeper problem. It's a prompt to look closer, not a rule: sometimes a long function is the clearest way to express a long algorithm.

The smells you'll meet most

SmellLooks likeUsual refactoring
Long functionComments labelling sections; scrolling to readExtract function
Long parameter listf(a, b, c, d, e, true)Introduce parameter object
Flag argumentA boolean that switches behaviourSplit into two functions
Duplicated knowledgeThe same rule in several placesExtract and give it one owner (DRY)
Data clumpsThe same fields always travel togetherExtract a type
Primitive obsessionMoney as float64, IDs and emails as bare stringsIntroduce a value object
Feature envyA method uses another type's data more than its ownMove the method
Message chainsa.B().C().D()Hide the delegate (Law of Demeter)
Shotgun surgeryOne change edits many filesMove related code together
Divergent changeOne file changes for many unrelated reasonsSplit by responsibility
Repeated switch on typeThe same switch kind in several placesReplace with polymorphism
Speculative generalityHooks, parameters and interfaces nobody usesInline or delete (YAGNI)
Dead codeUnreachable branches, unused functionsDelete it
Comments explaining confusing code// this is tricky because...Rename and restructure until the comment is unnecessary

Many smells are coupling and cohesion problems seen from a different angle: shotgun surgery is low cohesion, feature envy is behaviour living away from its data.

Responding to a smell

  1. Confirm it matters. Is this code changed often? Is it causing bugs? A smell in code nobody touches can wait.
  2. Get it under test — characterization tests if necessary.
  3. Refactor in small, behaviour-preserving steps, running tests after each one.
  4. Keep the refactor separate from feature or bug-fix commits.

Linters catch some smells mechanically — long functions, too many parameters, cyclomatic complexity, unused code (golangci-lint in Go, ESLint in TypeScript). The rest need a reviewer's judgment. The refactoring lesson works through several in Go.

Learn it in depth