Script Valley
Reading Other People's Code
Contributing to an Existing CodebaseLesson 5.5

How to refactor code you didn't write without breaking it

refactoring vs rewriting, small safe steps, test-first refactoring, rename → extract → move sequence, preserving behavior, when not to refactor

Refactor Means Change Structure, Not Behavior

Refactoring behavior vs structure diagram

Refactoring has a strict definition: restructure code without changing observable behavior. If you change behavior while refactoring, you've introduced a bug and a refactor simultaneously — and you won't know which caused a failure.

The Safe Refactoring Sequence

// Step 1: Ensure tests exist BEFORE touching anything
// If no tests exist, write characterization tests first

// Step 2: Rename — safest change with IDE refactoring tools
// Before: function usr_val(u) { ... }
// After:  function validateUser(user) { ... }
// Use IDE rename, not find-replace — it handles imports automatically

// Step 3: Extract — pull a block into a named function
// Before: long function doing 5 things
function processUser(data) {
  // 20 lines of validation...
  // 15 lines of normalization...
  // 10 lines of persistence...
}

// After: extracted functions with clear names
function processUser(data) {
  const validated = validateUserData(data);
  const normalized = normalizeUserFields(validated);
  return persistUser(normalized);
}

When Not to Refactor

Don't refactor code you don't understand yet — you'll break behavior you didn't know existed. Don't refactor across a large blast radius in one PR — break it into multiple small PRs. Don't refactor to your personal style preference if the team hasn't agreed on the change. The question is always: does this make the code more correct, readable, or maintainable for the team — or just more comfortable for me?