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

> Learn how to refactor existing JavaScript code for cleaner, more maintainable solutions. Apply practical tips like meaningful names, constants, and single-responsibility functions.

- Repository: [Ryan McDermott/clean-code-javascript](https://github.com/ryanmcdermott/clean-code-javascript)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/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`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md) section on meaningful variable names, clear identifiers improve searchability and self-documentation.

Replace timestamp abbreviations with descriptive constants:

```javascript
// 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:

```javascript
// 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.

```javascript
// 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.

```javascript
// 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`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/README.md) demonstrates how impure functions create hidden dependencies that make code unpredictable and difficult to parallelize.

Convert mutating operations into immutable updates:

```javascript
// 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:

```javascript
// 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`](https://github.com/ryanmcdermott/clean-code-javascript/blob/main/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.