Skip to content

Refactoring

Restructure code without changing behavior. Make one small change at a time, verify after each, commit or revert. Never be more than one step from a working system.

The loop

  1. Establish verification — set up a mechanism that catches regressions (tests, screenshot diffs, type checks, whatever fits the change)
  2. Verify green — confirm the baseline passes before touching anything
  3. Make the smallest possible change — one file, one concept, one moving part
  4. Verify — run the verification mechanism
  5. Pass → commit. Fail → investigate or revert.
  6. Repeat

Choosing a verification mechanism

Match the mechanism to what you're changing:

ChangeMechanism
Internal logic, APIsUnit/integration tests
Visual layout, CSS, DOM structureScreenshot diff tests
Type system, module boundariesType checker
Build output, bundle sizeBuild + size check
Database queriesQuery result assertions

Multiple mechanisms can layer. A light DOM refactor might use screenshot diffs for visual correctness and a build step to catch import errors.

Investigating failures

When verification fails, don't guess. Locate the exact difference:

  • Tests: read the assertion message — it tells you expected vs actual
  • Screenshots: generate a pixel-level diff image to see exactly what moved and where (use pixelmatch or similar)
  • Types: the compiler points to the exact line

If the change was truly small (one concept), the cause is usually obvious. If it's not obvious, the change was too big — revert and split it smaller.

Order matters

When converting a system from one architecture to another, work outside-in:

  • Convert outer containers before inner ones (a child component can't receive global styles if its parent still encapsulates them)
  • Migrate callers before implementations (or use an adapter during transition)
  • Move data before removing old schemas

Each ordering decision follows the same question: which direction keeps the system working at every step?

Example: shadow DOM to light DOM

Converting three web components from shadow DOM to light DOM:

  1. Establish verification — screenshot diff tests covering 11 app states
  2. Convert outermost component first (unicode-picker) — inner components still work because their shadow DOM is self-contained
  3. Convert inner component (char-grid) — fails screenshot diff due to box-sizing difference. Revert. Split into smaller steps:
    • Add CSS file alongside shadow DOM styles (verify: pass)
    • Remove shadow DOM (verify: fail — box-sizing shift)
    • Add box-sizing: content-box to affected element (verify: pass)
    • Remove redundant shadow DOM styles (verify: pass)
  4. Convert simplest component last (copy-toast) — trivial, passes

The key insight: the first attempt at step 3 made all changes at once. When it failed, the cause wasn't obvious. Reverting and splitting into sub-steps isolated the box-sizing issue immediately.