Skip to main content

Refactoring Principles

Based on Martin Fowler's refactoring guidance from martinfowler.com

Understanding Code Smells​

Definition: "A code smell is a surface indication that usually corresponds to a deeper problem in the system." (Kent Beck)

Key Characteristics​

  • Easy to spot - Quick to identify through visual inspection
  • Not always problematic - Smells are warning signs, not definitive problems
  • Prompt investigation - Use as indicators to look deeper, not triggers for automatic refactoring

Using Smells Effectively​

"The most useful smells are easily detectable and frequently point toward genuine improvements."

Reference: Code Smells

Common Code Smells Catalog​

Long Method/Function​

Description: Methods/functions that are too long

Why it matters:

  • Quick to identify visually
  • Often indicates multiple responsibilities
  • Makes code harder to understand and test

Solutions:

  • Extract Method/Function
  • Decompose into smaller, focused functions
  • Each function should do one thing well

Data Class​

Description: Classes with only fields and no behavior (getters/setters only)

Why it matters:

  • Violates object-oriented principle of bundling data with behavior
  • Behavior that operates on this data likely belongs in the class

Solutions:

  • Move methods that use the data into the data class
  • Apply Tell Don't Ask principle
  • Add behavior alongside the data

Large Class​

Description: Classes with too many responsibilities, fields, or methods

Why it matters:

  • Violates Single Responsibility Principle
  • Multiple reasons to change
  • Difficult to understand and maintain

Solutions:

  • Extract Class for cohesive subsets of behavior
  • Identify distinct responsibilities and split them
  • Look for groups of fields/methods that belong together

Long Parameter List​

Description: Functions with too many parameters

Why it matters:

  • Hard to understand what each parameter means
  • Difficult to call correctly
  • Often indicates missing abstraction

Solutions:

  • Introduce Parameter Object
  • Pass entire object instead of individual fields
  • Use Builder pattern for complex construction

Divergent Change​

Description: One class changes for multiple different reasons

Why it matters:

  • Violates Single Responsibility Principle
  • Different concerns tangled together
  • Changes to one concern affect unrelated functionality

Solutions:

  • Split class by responsibility
  • Extract classes for each reason to change
  • Identify and separate distinct concerns

Shotgun Surgery​

Description: One change requires modifications across many classes

Why it matters:

  • Related behavior is scattered
  • Hard to find all places that need changing
  • Easy to miss necessary updates

Solutions:

  • Move Method/Field to consolidate behavior
  • Inline Class if behavior is too fragmented
  • Create new class to centralize related behavior

Feature Envy​

Description: Method more interested in another class's data than its own

Why it matters:

  • Behavior separated from data it operates on
  • Violates encapsulation
  • Indicates wrong responsibility placement

Solutions:

  • Move Method closer to the data it uses
  • Apply Tell Don't Ask principle
  • Consider if behavior belongs in the other class

Primitive Obsession​

Description: Overuse of primitives instead of small objects

Why it matters:

  • Loses type safety and semantic meaning
  • Behavior scattered across codebase
  • Validation logic duplicated

Solutions:

  • Introduce Value Objects
  • Replace primitive with object (e.g., Money, PhoneNumber, EmailAddress)
  • Encapsulate validation and behavior in the object

Refactoring Flow​

Martin Fowler describes a systematic approach to refactoring:

  1. Break into functions - Decompose large methods into smaller ones
  2. Separate concerns - Separate calculation from formatting, business logic from presentation
  3. Organize by type - Use polymorphism for variant behavior
  4. Apply patterns - Progress from decomposition → separation → object-oriented patterns

When to Refactor​

The Rule of Three​

First time: Just do it Second time: Wince and duplicate Third time: Refactor

Specific Triggers​

Before adding features

  • Clean up the area where you'll be working
  • Makes the new feature easier to add
  • Prevents piling new code on top of messy code

When you encounter code smells

  • Investigate whether they indicate real problems
  • Refactor if they point to design issues

When tests are hard to write

  • Design for testability
  • Add seams for dependency injection
  • Separate concerns to isolate what you're testing

When making changes feels dangerous

  • Add tests first to establish safety net
  • Then refactor to make changes safer
  • Small, safe steps with test coverage

Refactoring Safety​

Prerequisites​

  1. Have tests - Comprehensive test coverage before refactoring
  2. Small steps - Make tiny changes and test after each
  3. Commit often - Easy to revert if something goes wrong

Process​

  1. Ensure tests pass before starting
  2. Make one small refactoring
  3. Run tests
  4. Commit if tests pass
  5. Repeat

Anti-Patterns to Avoid​

  • Refactoring without tests - Dangerous; add tests first
  • Big bang refactoring - Small incremental changes are safer
  • Ignoring code smells - They usually indicate real problems
  • Premature abstraction - Wait for duplication before abstracting
  • Over-engineering - Simplest solution that works is usually best

Educational Approach​

"An effective teaching approach involves selecting individual smells periodically and having teams identify and discuss them collaboratively. This gradual process helps develop programming expertise across teams."

  • Pick one smell at a time to focus on
  • Have team discussions about examples
  • Build shared understanding gradually
  • Even junior developers can spot smells before fully understanding problems