How to Refactor Existing JavaScript Code: A Practical Guide to Clean Code Principles

Refactor existing JavaScript code by replacing cryptic abbreviations with meaningful variable names, extracting magic numbers into searchable constants, splitting multi-purpose functions into single-responsibility units, eliminating boolean flag arguments, and favoring object composition over deep inheritance hierarchies.

The ryanmcdermott/clean-code-javascript repository adapts Robert C. Martin’s Clean Code principles for modern JavaScript, providing concrete before-and-after patterns for developers who need to refactor existing JavaScript code. Each guideline in the README.md file demonstrates how to transform messy, tightly-coupled logic into readable, testable modules that minimize cognitive load and reduce bugs.

Refactor Variables and Naming Conventions

Meaningful identifiers form the foundation of readable code. The repository emphasizes that names should reveal intent and make the codebase searchable.

Use Meaningful and Pronounceable Names

Avoid cryptic abbreviations that require mental translation. According to the README.md section on meaningful variable names, clear identifiers improve searchability and self-documentation.

Replace timestamp abbreviations with descriptive constants:

// Before: cryptic and unsearchable
const yyyymmdstr = moment().format('YYYY/MM/DD');

// After: explicit constant and clear variable name
const DATE_FORMAT = 'YYYY/MM/DD';
const currentDate = moment().format(DATE_FORMAT);

Extract Searchable Constants

Magic numbers and string literals should become named constants. This centralizes changes and makes intent explicit, as implemented in the repository’s searchable names guidelines.

Add Explanatory Variables

Break complex expressions into well-named intermediate variables to clarify intent and reduce line length. This technique targets the Avoid Mental Mapping principle—give loop iterators and temporary values descriptive names like location instead of l to keep the reader’s mental model aligned with the code.

Eliminate Redundant Context

Do not repeat information that the surrounding object or scope already conveys. The repository’s Don’t Add Unneeded Context guideline recommends keeping object literals concise by removing redundant prefixes from property names.

Refactor Functions for Single Responsibility

Functions are the primary unit of organization in JavaScript. The repository mandates that each function should do exactly one thing and do it well.

Limit Functions to One Purpose

When you encounter a function that performs multiple operations, extract helper functions to isolate responsibilities. This simplification makes testing easier and reduces the risk of unintended side effects when modifying code.

Split a function that filters and emails clients into discrete steps:

// Before: violates single-responsibility principle
function emailClients(clients) {
  clients.forEach(c => {
    const record = db.lookup(c);
    if (record.isActive()) {
      email(c);
    }
  });
}

// After: composed of single-purpose functions with descriptive names
function getActiveClients(clients) {
  return clients.filter(isActiveClient);
}

function isActiveClient(client) {
  const record = db.lookup(client);
  return record.isActive();
}

function emailActiveClients(clients) {
  getActiveClients(clients).forEach(email);
}

Remove Flag Arguments

Boolean flags indicate a function does more than one thing. The repository’s Don’t Use Flags as Function Parameters rule requires splitting such functions into separate, explicitly named alternatives.

// Before: behavior changes based on flag parameter
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

// After: two clear functions with single responsibilities
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

Prefer Default Parameters Over Conditionals

Use ES6 default parameter syntax instead of manual short-circuiting with ||. This reduces conditional noise and makes the function’s API contract clearer, as shown in the repository’s default parameters section.

// Before: manual fallback logic obscures intent
function createMicrobrewery(name) {
  const breweryName = name || 'Hipster Brew Co.';
  // …
}

// After: default value declared in signature
function createMicrobrewery(name = 'Hipster Brew Co.') {
  // …
}

Eliminate Side Effects and Mutable State

Functions should be pure: they should accept inputs, return outputs, and avoid mutating external state. The Avoid Side Effects section in README.md demonstrates how impure functions create hidden dependencies that make code unpredictable and difficult to parallelize.

Convert mutating operations into immutable updates:

// Before: mutates the original cart array (side effect)
function addItemToCart(cart, item) {
  cart.push({ item, date: Date.now() });
}

// After: returns a new cart, leaving the original untouched
function addItemToCart(cart, item) {
  return [...cart, { item, date: Date.now() }];
}

Favor Composition Over Inheritance

Deep inheritance hierarchies create brittle coupling between parent and child classes. The repository’s Prefer Composition Over Inheritance guideline recommends assembling behavior through object composition to improve flexibility and reduce coupling.

Replace inheritance with composition:

// Bad: tight coupling through inheritance
class EmployeeTaxData extends Employee { /* … */ }

// Good: composed relationship
class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
}

Apply SOLID Principles to Modules

The README.md dedicates a section to SOLID principles adapted for JavaScript. When you refactor existing JavaScript code, apply these architectural constraints:

  • Single Responsibility: Each module or class should have one reason to change.
  • Open/Closed: Design modules that are open for extension but closed for modification.
  • Liskov Substitution: Ensure derived classes can substitute their base classes without altering correctness.
  • Interface Segregation: Depend on small, focused interfaces rather than large, monolithic ones.
  • Dependency Inversion: Depend on abstractions (interfaces or functions), not concrete implementations.

Modernize Asynchronous Patterns

Replace callback pyramids with Promises or async/await syntax. The repository’s Use Promises, Not Callbacks guideline states that flattening asynchronous flow reduces nesting and improves readability, making error handling more straightforward.

Establish Testing and Formatting Discipline

Clean code requires verification. The repository’s Testing guidelines demand:

  • One concept per test
  • Isolated test cases without shared state
  • High coverage to enable confident refactoring

Additionally, configure a consistent linter and formatter (ESLint with Prettier) to eliminate style churn. Automated formatting allows reviewers to focus on logic rather than indentation.

Step-by-Step Refactoring Strategy

When applying these principles to a legacy codebase, use an incremental approach to minimize risk:

  1. Configure linting with rules targeting no-magic-numbers and no-duplicate-imports.
  2. Identify the worst offenders: locate functions exceeding 20 lines or using cryptic variable names.
  3. Apply one category at a time: refactor naming conventions in one commit, then function structure in the next.
  4. Add unit tests before changing function responsibilities to ensure behavior preservation.
  5. Iterate until the codebase consistently follows the clean-code guidelines documented in the repository.

Summary

  • Refactor variables by replacing abbreviations with meaningful, pronounceable names and extracting magic numbers into searchable constants.
  • Refactor functions to ensure each performs a single action, uses default parameters instead of short-circuiting, and eliminates boolean flag arguments.
  • Remove side effects by writing pure functions that return new data structures rather than mutating inputs.
  • Prefer composition over inheritance to reduce class coupling and improve flexibility.
  • Apply SOLID principles to module architecture for long-term maintainability.
  • Modernize async code using async/await instead of nested callbacks.
  • Automate formatting and maintain high test coverage to support continuous refactoring.

Frequently Asked Questions

What is the first step when refactoring legacy JavaScript code?

Start by establishing a safety net of unit tests and configuring automated linting with ESLint. According to the ryanmcdermott/clean-code-javascript guidelines, you should identify the most complex functions and variable naming violations first, then apply fixes incrementally to avoid introducing regressions.

How do I know when to split a function into smaller functions?

Split a function when it performs multiple actions described by the word "and" in its name, or when it requires comments to explain sections of its logic. The repository mandates that functions should do one thing, meaning they should only operate on one level of abstraction and have a single reason to change.

How should I handle side effects when refactoring existing JavaScript functions?

Convert impure functions that mutate external state into pure functions that accept inputs and return new values. For example, replace Array.prototype.push operations with spread syntax [...array, newItem] to return new arrays without modifying the original data, following the Avoid Side Effects principle.

Is inheritance ever acceptable in JavaScript clean code?

While inheritance is occasionally useful for true taxonomic relationships, the repository strongly recommends preferring composition over inheritance in most cases. Composition avoids the fragile base class problem and allows behavior to be mixed and matched at runtime rather than locked in through rigid class hierarchies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →