Back to Blog
Code Quality9 min

When and How to Refactor Code: A Practical Guide

Learn to identify code smells and apply proven refactoring techniques to improve your codebase without breaking things.

By Development TeamFebruary 25, 2026

"Refactoring is a disciplined technique for restructuring code without changing its external behavior." — Martin Fowler

Refactoring isn't about rewriting everything from scratch. It's about making small, safe improvements that compound over time.

When to Refactor

✅ Refactor When:

  • Adding a new feature — Clean the area first ("Boy Scout Rule")
  • Fixing a bug — If code is hard to understand, refactor before fixing
  • Code review — When you notice duplication or complexity
  • Performance issues — After profiling identifies bottlenecks
  • Before a major release — Clean up technical debt

❌ Don't Refactor When:

  • Just before a deadline — Risk vs. reward isn't worth it
  • Code is stable and rarely changed — "If it ain't broke..."
  • You don't have tests — Write tests first!
  • It's easier to rewrite — Sometimes a fresh start is better

Common Code Smells

1. Long Method

// ❌ Smell: 150-line function
function processOrder(order) {
  // Validate order
  // ...50 lines
  
  // Calculate totals
  // ...40 lines
  
  // Apply discounts
  // ...30 lines
  
  // Save to database
  // ...30 lines
}

// ✅ Refactored: Extract methods
function processOrder(order) {
  validateOrder(order);
  const totals = calculateTotals(order);
  const finalPrice = applyDiscounts(totals, order.coupon);
  return saveOrder(order, finalPrice);
}

2. Duplicate Code

// ❌ Smell: Same logic repeated
function createUser(data) {
  const user = { ...data };
  user.createdAt = new Date();
  user.updatedAt = new Date();
  user.status = 'active';
  return db.save(user);
}

function createAdmin(data) {
  const admin = { ...data };
  admin.createdAt = new Date();
  admin.updatedAt = new Date();
  admin.status = 'active';
  admin.role = 'admin';
  return db.save(admin);
}

// ✅ Refactored: Extract common logic
function createUserBase(data, role = 'user') {
  return {
    ...data,
    createdAt: new Date(),
    updatedAt: new Date(),
    status: 'active',
    role,
  };
}

function createUser(data) {
  return db.save(createUserBase(data));
}

function createAdmin(data) {
  return db.save(createUserBase(data, 'admin'));
}

3. Long Parameter List

// ❌ Smell: Too many parameters
function createInvoice(
  customerId,
  items,
  taxRate,
  discount,
  currency,
  paymentMethod,
  shippingAddress,
  billingAddress,
  notes
) {
  // ...
}

// ✅ Refactored: Use object parameter
function createInvoice(options) {
  const {
    customerId,
    items,
    taxRate = 0.21,
    discount = 0,
    currency = 'EUR',
    paymentMethod,
    shippingAddress,
    billingAddress,
    notes = '',
  } = options;
  // ...
}

4. Large Class

// ❌ Smell: Class does everything
class User {
  login() { }
  logout() { }
  sendEmail() { }
  saveToDatabase() { }
  generatePDF() { }
  processPayment() { }
  uploadImage() { }
}

// ✅ Refactored: Split responsibilities
class User {
  constructor(data) {
    this.data = data;
  }
}

class UserAuth {
  login(user) { }
  logout(user) { }
}

class UserRepository {
  save(user) { }
  find(id) { }
}

class EmailService {
  send(to, subject, body) { }
}

class PaymentProcessor {
  charge(user, amount) { }
}

5. Primitive Obsession

// ❌ Smell: Using primitives instead of objects
function calculateShipping(
  weight,      // number (kg)
  distance,    // number (km)
  isExpress,   // boolean
  country      // string
) {
  // ...
}

// ✅ Refactored: Value objects
class Weight {
  constructor(kg) {
    if (kg <= 0) throw new Error('Invalid weight');
    this.kg = kg;
  }
}

class Distance {
  constructor(km) {
    if (km <= 0) throw new Error('Invalid distance');
    this.km = km;
  }
}

class ShippingOption {
  constructor(isExpress, country) {
    this.isExpress = isExpress;
    this.country = country;
  }
}

function calculateShipping(weight, distance, option) {
  // Type safety + validation built-in
}

6. Feature Envy

// ❌ Smell: Method more interested in another class
class Order {
  getTotalPrice() {
    let total = 0;
    for (const item of this.items) {
      total += item.quantity * item.product.price;
    }
    return total;
  }
}

// ✅ Refactored: Move logic to appropriate class
class OrderItem {
  getPrice() {
    return this.quantity * this.product.price;
  }
}

class Order {
  getTotalPrice() {
    return this.items.reduce((sum, item) => sum + item.getPrice(), 0);
  }
}

Refactoring Techniques

1. Extract Method

// Before
function printOwing() {
  printBanner();
  
  let outstanding = 0;
  for (const order of orders) {
    outstanding += order.amount;
  }
  
  console.log(`Name: ${name}`);
  console.log(`Amount: ${outstanding}`);
}

// After
function printOwing() {
  printBanner();
  const outstanding = calculateOutstanding();
  printDetails(outstanding);
}

function calculateOutstanding() {
  return orders.reduce((sum, order) => sum + order.amount, 0);
}

function printDetails(outstanding) {
  console.log(`Name: ${name}`);
  console.log(`Amount: ${outstanding}`);
}

2. Replace Conditional with Polymorphism

// Before
function getSpeed(vehicle) {
  switch (vehicle.type) {
    case 'car':
      return vehicle.enginePower * 1.2;
    case 'boat':
      return vehicle.enginePower * 0.8;
    case 'plane':
      return vehicle.enginePower * 2.0;
  }
}

// After
class Vehicle {
  getSpeed() {
    throw new Error('Must implement getSpeed');
  }
}

class Car extends Vehicle {
  getSpeed() {
    return this.enginePower * 1.2;
  }
}

class Boat extends Vehicle {
  getSpeed() {
    return this.enginePower * 0.8;
  }
}

class Plane extends Vehicle {
  getSpeed() {
    return this.enginePower * 2.0;
  }
}

3. Introduce Parameter Object

// Before
function createDateRange(startYear, startMonth, startDay, endYear, endMonth, endDay) {
  // ...
}

// After
class DateRange {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
}

function createDateRange(range) {
  // ...
}

Safe Refactoring Process

1. Write Tests First

// Before refactoring, ensure tests exist
test('calculates total with discount', () => {
  const order = new Order([item1, item2]);
  expect(order.getTotalWithDiscount(0.1)).toBe(90);
});

2. Make Small Changes

Refactor in tiny steps. After each step:

  1. Run tests
  2. Commit if tests pass
  3. Continue

3. Don't Refactor and Add Features Simultaneously

Separate commits:

  • Commit 1: Refactor (tests stay green)
  • Commit 2: Add new feature

Tools to Help Refactoring

IDE Refactoring Tools

  • VS Code: Rename symbol, extract method, move file
  • IntelliJ IDEA: Advanced refactorings (extract interface, inline, etc.)
  • TypeScript: Refactoring with type safety

Linters and Static Analysis

  • ESLint: Detect code smells
  • SonarQube: Technical debt tracking
  • CodeClimate: Automated code review
  • ScanMyCode.dev: AI-powered refactoring recommendations

Measuring Refactoring Success

Metrics to Track

  • Cyclomatic complexity: Lower is better (<10 per function)
  • Code coverage: Should stay same or improve
  • Build time: Shouldn't increase significantly
  • Bug rate: Should decrease over time
  • Team velocity: Should improve as code gets cleaner

Common Refactoring Mistakes

❌ Big Bang Refactoring

Rewriting entire modules at once. High risk, hard to review, difficult to merge.

❌ Refactoring Without Tests

How do you know you didn't break anything?

❌ Premature Optimization

Don't refactor for performance without profiling data.

❌ Ignoring the Team

Large refactorings need team buy-in and coordination.

The Boy Scout Rule

"Leave the code cleaner than you found it."

Don't wait for the perfect time to refactor. Make small improvements every time you touch code:

  • Rename a variable for clarity
  • Extract a long method
  • Remove dead code
  • Fix a typo in comments

Conclusion

Refactoring is continuous improvement, not a one-time event. Start small, test often, and make incremental progress. Your future self will thank you.

Need help identifying refactoring opportunities in your codebase? Get a code quality audit and receive specific refactoring recommendations within 24 hours.

refactoringcode qualitytechnical debtbest practices

Ready to improve your code?

Get an AI-powered code audit with actionable recommendations. Results in 24 hours.

Start Your Audit