Skip to content

Instantly share code, notes, and snippets.

@finereli
Created January 29, 2026 21:26
Show Gist options
  • Select an option

  • Save finereli/8eb76db9e9b5052ca756c0306f35642c to your computer and use it in GitHub Desktop.

Select an option

Save finereli/8eb76db9e9b5052ca756c0306f35642c to your computer and use it in GitHub Desktop.
Cursor AI Refactoring Skill - Safe, disciplined refactoring with 80/20 prioritization

Safe Refactoring Catalog

These transformations are safe because they can be verified without tests.

Extract Method/Function

When: Code is too long, has comments explaining sections, or you need to reuse a portion.

Why safe: Extracted code is literally copy-pasted. Compiler catches missing variables.

# Before
def process_order(order):
    # validate
    if order.total < 0:
        raise ValueError("Invalid total")
    if not order.items:
        raise ValueError("No items")
    
    # calculate discount
    discount = 0
    if order.total > 100:
        discount = order.total * 0.1
    
    return order.total - discount

# After
def process_order(order):
    validate_order(order)
    discount = calculate_discount(order.total)
    return order.total - discount

def validate_order(order):
    if order.total < 0:
        raise ValueError("Invalid total")
    if not order.items:
        raise ValueError("No items")

def calculate_discount(total):
    if total > 100:
        return total * 0.1
    return 0

Inline Method/Function

When: Function body is as clear as its name, or you need full picture before re-extracting differently.

Why safe: Mechanical replacement, compiler catches type mismatches.

// Before
func isAdult(age int) bool {
    return age >= 18
}

func canVote(person Person) bool {
    return isAdult(person.Age) && person.Registered
}

// After (if isAdult adds no clarity)
func canVote(person Person) bool {
    return person.Age >= 18 && person.Registered
}

Rename

When: Name doesn't reflect purpose, or purpose has changed.

Why safe: Compiler/linter catches all references. Find-and-replace with whole-word matching.

Caution: Watch for string references (API endpoints, serialization), dynamic access, cross-file public APIs.

Move Method/Function

When: Function more closely related to another module/class.

Why safe: Import errors catch missing references.

// Before: in utils.dart
String formatCurrency(double amount) {
  return '\$${amount.toStringAsFixed(2)}';
}

// After: moved to money.dart (where Money class lives)
// Update all imports from 'utils.dart' to 'money.dart'

Extract Variable

When: Expression is complex or used multiple times.

Why safe: Pure mechanical extraction, no logic change.

# Before
if user.subscription.plan.price > 100 and user.subscription.plan.price < 500:
    apply_mid_tier_discount(user.subscription.plan.price)

# After
price = user.subscription.plan.price
if price > 100 and price < 500:
    apply_mid_tier_discount(price)

Inline Variable

When: Variable adds no explanatory value.

Why safe: Direct substitution.

// Before
const basePrice = order.basePrice;
return basePrice;

// After
return order.basePrice;

Split Loop

When: Loop does multiple unrelated things.

Why safe: Same iterations, same operations, just separated.

// Before
var sum int
var product int = 1
for _, v := range values {
    sum += v
    product *= v
}

// After
var sum int
for _, v := range values {
    sum += v
}

var product int = 1
for _, v := range values {
    product *= v
}

Note: Yes, "slower" (two loops). Optimize later if profiling shows it matters. Clarity enables further refactoring.

Replace Nested Conditional with Guard Clauses

When: Deep nesting obscures main logic.

Why safe: Same conditions, same outcomes, different structure.

# Before
def get_payment_amount(employee):
    if employee.is_separated:
        result = separated_amount(employee)
    else:
        if employee.is_retired:
            result = retired_amount(employee)
        else:
            result = normal_amount(employee)
    return result

# After
def get_payment_amount(employee):
    if employee.is_separated:
        return separated_amount(employee)
    if employee.is_retired:
        return retired_amount(employee)
    return normal_amount(employee)

Seams: Places to Change Behavior Without Editing

A "seam" is where you can alter program behavior without modifying code at that location.

Object Seam

Pass a dependency rather than hard-coding it.

# Before: hard to test or modify behavior
def send_notification(user, message):
    client = SMTPClient("mail.server.com")
    client.send(user.email, message)

# After: seam at the client parameter
def send_notification(user, message, client=None):
    if client is None:
        client = SMTPClient("mail.server.com")
    client.send(user.email, message)

Configuration Seam

Behavior controlled by configuration rather than code.

func ProcessPayment(amount float64) error {
    if config.PaymentProvider == "stripe" {
        return processStripe(amount)
    }
    return processSquare(amount)
}

Duplicate Before Unifying

To extract common code from two places:

  1. Make them identical (even if that means temporary duplication)
  2. Then extract the common code
// Two similar but different functions
function processUserOrder(order: Order) {
  validateUser(order.userId);
  const tax = order.total * 0.08;
  const shipping = 5.99;
  return order.total + tax + shipping;
}

function processGuestOrder(order: Order) {
  const tax = order.total * 0.08;
  const shipping = order.items > 2 ? 0 : 5.99;  // different!
  return order.total + tax + shipping;
}

// Step 1: Make shipping calculation explicit in both
function processUserOrder(order: Order) {
  validateUser(order.userId);
  const tax = order.total * 0.08;
  const shipping = calculateShipping(order, false);
  return order.total + tax + shipping;
}

function processGuestOrder(order: Order) {
  const tax = order.total * 0.08;
  const shipping = calculateShipping(order, true);
  return order.total + tax + shipping;
}

// Step 2: Now extract the common part
function calculateOrderTotal(order: Order, isGuest: boolean): number {
  const tax = order.total * 0.08;
  const shipping = calculateShipping(order, isGuest);
  return order.total + tax + shipping;
}
name description
refactor
Guide safe, disciplined code refactoring using small, verifiable steps. Use when restructuring code, extracting functions, renaming, or improving code structure without changing behavior.

Refactoring

Change code structure without changing behavior. Every step must be small enough to verify before proceeding.

The Golden Rule

One thing at a time. Never mix:

  • Refactoring with behavior changes
  • Multiple refactoring operations
  • Cleanup with feature work

Each step should be a single, reversible transformation.

Before Refactoring: Find What Matters

Don't refactor everything. Apply 80/20: find the few changes that deliver most value.

Identify Code Smells

Scan for these high-signal problems:

Smell Signal Impact
Long function (>50 lines) Hard to understand, test, modify High
Deep nesting (>3 levels) Complex control flow, bug-prone High
Duplicate code Changes require multiple edits High
Long parameter list (>4) Hard to call correctly Medium
Feature envy Method uses another class more than its own Medium
Data clumps Same fields always appear together Medium
Primitive obsession Strings/ints where objects belong Low-Medium
Dead code Confusion, maintenance burden Low

Prioritize: Impact vs Effort

Score each smell:

  • Pain: How much does this hurt? (bugs, slow changes, confusion)
  • Traffic: How often is this code touched?
  • Effort: How many steps to fix?

Target: High pain × High traffic × Low effort

The 80/20 Filter

Ask before each refactoring:

  1. Is this code actively causing problems or blocking work?
  2. Will I or others need to modify this code soon?
  3. Is the fix quick relative to the benefit?

If not all three: skip it. Move on to what matters.

Output: A Short List

Produce 2-5 high-priority targets, not a comprehensive cleanup plan. Example:

Priority refactorings:
1. processOrder() - 120 lines, touched weekly, extract 3 methods
2. UserService.validate() - 5-level nesting, guard clauses
3. Duplicate discount logic in Cart and Checkout - unify

Stop analyzing. Start with #1.

Workflow

Do not plan and execute all refactorings at once. That's a rewrite.

Loop:

  1. Refactor - One transformation
  2. Verify - Run tests, check compiler, inspect diff
  3. Decide - Based on how code looks now, choose next step

The right next step often becomes clear only after the previous step completes.

Verification Methods

Method Use when
Compiler/type checker Always - catches missing references
Unit tests When available - confirms behavior preserved
Visual inspection Simple transforms - before/after obviously equivalent
Manual test No automated coverage - verify key paths work

Calibrating Step Size

Factors that enable larger steps:

  • Strong test coverage
  • Static typing
  • High model capability

When in doubt, smaller steps. You can go faster once verified.

Without Test Coverage

Use only transformations verifiable through:

  • Visual inspection (before/after obviously equivalent)
  • Compiler (rename errors catch missing references)
  • Mechanical transformation (no judgment, just moving code)

If you can't verify a step, make it smaller.

Safe Transformations

See catalog.md for the full catalog with examples. Quick reference:

Situation Transformation
Long function Extract Method
Unclear name Rename
Complex expression Extract Variable
Code in wrong file Move Function
Deep nesting Guard Clauses
Loop does too much Split Loop
Two similar functions Duplicate → Unify
Need test seam Add optional parameter

Making Code Temporarily Worse

Sometimes necessary. Two patterns:

Duplicate before unifying - Make two functions identical (even ugly), then extract common code.

Expand before contracting - Expand complex conditionals to explicit cases, then identify patterns and simplify.

What NOT to Do

  • Don't mix refactoring with features - Extract method (commit). Fix edge case (separate commit).
  • Don't create speculative abstractions - Extract interface only when you have two implementations NOW.
  • Don't refactor without purpose - Finish what you start, or don't start.
  • Don't skip verification - Make 1 change, verify, repeat. Not 10 changes then test.

Why Smaller Functions Help LLMs

  • Search/replace tools struggle with deep indentation
  • Smaller functions = smaller context needed
  • Flat structure = fewer edit conflicts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment