Departments / developers / refactor

refactor

Use when the user asks to clean up, simplify, rename, extract, inline, or restructure code without changing behaviour. Performs Fowler-style refactorings in small steps, running the test suite after every step; never proceeds when tests fail.

Department

Developers

Safety

writes-local
Writes locally

Supported stacks

Stack-agnostic — no detection required.

When to use

Do not use this skill to change behaviour (that is a feature or bugfix). If a refactor reveals a bug, stop, note it, and address it as a separate change.

Inputs

Outputs

Tool dependencies

Procedure

  1. Confirm the safety net. Run the test suite. If it is red, stop — refactoring on a red suite is unsafe. If coverage for the target is absent, invoke test-writer to add characterisation tests.
  2. Name the refactoring. Pick one from references/refactoring-patterns.md. If the intended change does not match a named refactoring, it is probably behaviour change in disguise — stop and clarify.
  3. Plan the smallest safe step. Large refactorings are sequences of small ones: extract variable, then extract method, then move method, rather than one big rewrite.
  4. Apply one step. Change only the files the step requires. Do not fix unrelated issues along the way.
  5. Run the formatter. Run the linter. Run the tests. If anything fails, revert the step and reconsider (the step was too big, or missed a call site).
  6. Commit the step with a message like refactor: extract method sendWelcomeEmail from UserService.create (Conventional Commits, see the commit-message skill).
  7. Repeat from step 3 until the goal is reached.
  8. Emit the summary listing the steps.

If the tooling supports safe rename (TS, Rust, Go with gopls), use it rather than hand-edits — it is mechanical and exhaustive.

Examples

Happy path: extract method + rename

Before (src/orders/create.ts, tests pass):

export async function createOrder(userId: string, items: Item[]) {
  // validate
  if (items.length === 0) throw new Error('empty');
  for (const it of items) {
    if (it.quantity <= 0) throw new Error('quantity');
    if (it.priceCents < 0) throw new Error('price');
  }

  // compute total
  let total = 0;
  for (const it of items) total += it.quantity * it.priceCents;

  // persist
  const id = await db.insert('orders', { userId, total, createdAt: Date.now() });
  for (const it of items) await db.insert('order_items', { orderId: id, ...it });
  return { id, total };
}

Step 1: extract method validate (run tests after).

function validate(items: Item[]) {
  if (items.length === 0) throw new Error('empty');
  for (const it of items) {
    if (it.quantity <= 0) throw new Error('quantity');
    if (it.priceCents < 0) throw new Error('price');
  }
}

Step 2: extract method computeTotal.

const computeTotal = (items: Item[]) =>
  items.reduce((s, it) => s + it.quantity * it.priceCents, 0);

Step 3: extract method persist. Run tests.

Step 4: rename computeTotal -> sumLineItemsCents using language-server rename. Run tests.

After:

export async function createOrder(userId: string, items: Item[]) {
  validate(items);
  const total = sumLineItemsCents(items);
  return persist(userId, items, total);
}

Summary:

1. extract-method: validate (src/orders/create.ts:3-10)
2. extract-method: computeTotal (src/orders/create.ts:13-14)
3. extract-method: persist (src/orders/create.ts:17-20)
4. rename: computeTotal -> sumLineItemsCents (language-server rename; 2 call sites updated)

Edge case: replace conditional with polymorphism when new type keeps appearing

Before (Python):

def discount(order):
    if order.kind == "retail":
        return 0
    elif order.kind == "wholesale":
        return 0.10 * order.total
    elif order.kind == "employee":
        return 0.25 * order.total
    raise ValueError(order.kind)

Every new customer type touches this function; tests exist for each branch.

Step 1: introduce a strategy protocol, keep the old function dispatching to it.

class DiscountPolicy(Protocol):
    def rate(self, order: Order) -> float: ...

class Retail:
    def rate(self, order): return 0
class Wholesale:
    def rate(self, order): return 0.10
class Employee:
    def rate(self, order): return 0.25

POLICIES = {"retail": Retail(), "wholesale": Wholesale(), "employee": Employee()}

def discount(order):
    policy = POLICIES.get(order.kind)
    if not policy: raise ValueError(order.kind)
    return policy.rate(order) * order.total

Run tests — green.

Step 2: move kind onto Order as a policy field constructed at creation time; remove the dictionary lookup. Run tests.

Step 3: delete the now-unused string kind field and its migrations. Run tests and integration tests.

Each step was behaviour-preserving; the conditional is gone; adding a new customer type is now one new class.

Constraints

Quality checks

Customise for your organisation

refactor

The LLM will rewrite this skill for your environment. Your API key and form inputs stay in your browser — only the skill and your environment go to OpenRouter.

One line. Be specific — cloud, language, framework, orchestrator.

Free text that steers the rewrite. Leave blank if nothing specific.

cost estimate: