Shiva — The Transformer (Refactoring)
Shiva destroys only to rebuild: every transformation is deliberate, tested, and reversible.
Prerequisites — never skip
- Never refactor code without tests covering current behavior. No tests? Write characterization tests first: capture what the code actually does (including its bugs), then refactor against them.
- Confirm the code is worth transforming: it changes often, or it blocks a feature. Stable code nobody touches stays as it is.
- Run the full test suite green before touching anything. That is your baseline.
The rules of transformation
- Behavior-preserving only. A refactor changes structure, never observable behavior. If you found a bug, fix it in a separate commit/PR first.
- Never mix refactoring with feature work. One PR = one refactor. Reviewers cannot verify "moved AND changed" diffs.
- Small reversible steps: rename, extract, inline, move — one mechanical step at a time, tests green after each. Commit at every green state so any step can be reverted alone.
- Prefer automated transforms (IDE rename/extract,
ruff --fix,jscodeshift,ast-grep) over hand-editing — they don't typo. - Keep the old and new paths compilable at every commit. Long-lived "big bang" refactor branches rot; land incrementally behind the same interface.
Big rewrites — strangler fig
- Never rewrite a live system in place. Put a seam (router, facade, adapter) in front, build the new implementation beside the old, and migrate callers one by one.
- Route a small percentage of traffic to the new path first; compare outputs (shadow mode) before cutting over.
- Delete the old path only when zero callers remain — then delete it completely, same week. A strangler fig that never strangles is double the debt.
Deleting dead code
- Delete aggressively: unused functions, unreachable branches, commented-out blocks, feature flags past their expiry, exports with no importers.
- Verify death first:
grep/rgacross all repos, check dynamic call sites (getattr, string dispatch, DI containers), check production logs/metrics for the endpoint. - Deletion-only PRs merge fast — keep them separate from other changes.
When NOT to refactor
- Right before a deadline or during an incident — stabilize first.
- Code you don't understand yet — read, test, and instrument before reshaping.
- Working code slated for deletion or replacement within a quarter.
- "While I'm here" drive-by refactors inside a feature PR — file an issue instead.
Measure before/after
- Record the motivating metric before starting: cyclomatic complexity, test runtime, p95 latency, lines touched per change, onboarding questions.
- After landing, confirm the metric moved. A refactor that improves nothing measurable was churn.
Before every refactor PR — checklist
- Tests (or new characterization tests) cover current behavior and pass
- Diff contains zero behavior changes and zero feature work
- Steps are small, each commit green and independently revertible
- Dead code found along the way is deleted (own PR if large)
- Before/after metric recorded in the PR description