← Back to blog

Code Refactoring in Practice: From "It Works" to "Easy to Change"

Refactoring is not about "rewriting bad code" — it is about improving code structure step by step, with verification at each step. This article covers when to refactor, how to refactor safely, common refactoring techniques, and how to make refactoring a daily habit — for backend and frontend developers maintaining medium-to-large projects.

The Bottom Line: Refactoring Is Not “Rewriting” — It Is “Improving”

Many developers react to bad code with “let’s rewrite it,” but rewriting carries much higher risk than refactoring — you may lose edge case handling, introduce new bugs, and business stakeholders will not wait for you to “finish the rewrite.”

The core principle of refactoring: change one thing at a time, and make sure it still works after each change.


1. When to Refactor

Signals Worth Acting On

SignalSymptomSolution
Duplicated codeSame logic in multiple placesExtract function or class
Long functionOver 50 linesSplit into smaller functions
Large classOver 500 linesSplit responsibilities
Tight couplingChanging one thing breaks manyIntroduce interfaces or DI
Poor namingVariable names do not express intentRename

When NOT to Refactor

  • Code is about to be replaced (scheduled for decommission)
  • Code is rarely modified (stable, untouched)
  • No test coverage and cannot be added (legacy system nearing end of life)

2. Safe Refactoring Steps

2.1 Write Tests First

describe('formatPrice', () => {
  it('formats price correctly', () => {
    expect(formatPrice(1000)).toBe('¥1,000.00');
    expect(formatPrice(2500.5)).toBe('¥2,500.50');
  });
});

2.2 Small Steps

One refactoring operation at a time, run tests after each:

// Step 1: Rename
function calc(a: number, b: number) { ... }
// → function calculateDiscount(originalPrice: number, level: string) { ... }

// Step 2: Extract function
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
const tax = total * 0.1;
// → function calculateSubtotal(items: Item[]): number { ... }
// → function calculateTax(subtotal: number): number { ... }

2.3 Commit Each Step

git commit -m "refactor: extract calculateDiscount function, replace inline calculation"

3. Common Refactoring Techniques

TechniqueBest ForNote
RenamePoor namingIDE auto-refactoring, safe
Extract FunctionLong functions, duplicationExtract logic into independent function
Extract VariableHard-to-understand expressionsUse meaningful variable names
Decompose ConditionLong conditionalsSplit into multiple functions
Introduce ParameterHard-coded valuesReplace with parameter
Move MethodMethod in wrong classMove to more appropriate class

4. Make Refactoring a Habit

The Boy Scout Rule

“Leave the camp cleaner than you found it” — every time you modify code, improve it slightly. No need to schedule dedicated refactoring time.

Time Budget

ScenarioSuggested Time
Bug fix5-10 min of refactoring
Small feature15-30 min refactoring affected code first
Large featurePlan 1-2 days for refactoring
Code ReviewNote smells for next refactoring session

Summary

PrincipleNote
Test firstRefactoring without tests is gambling
Small stepsOne refactoring operation at a time
Frequent commitsOne commit per step, easy to rollback
Daily habitNo “refactoring month”
Boy Scout ruleImprove a little every time

Refactoring is not about “waiting until the code is terrible and then doing a big overhaul” — it is about “making it a little better every time you pass through.” A codebase that improves 1% every day, versus one that accumulates 1% technical debt every day, will be vastly different in six months.

Need code refactoring or technical consulting? Contact us — tell us about your project scale and pain points, feasibility within 24 hours.

FAQ

What is the difference between refactoring and rewriting?

Refactoring improves internal structure without changing external behavior — functionality stays the same, code gets better. Rewriting means starting from scratch — functionality may change, code is completely rewritten. Every refactoring step is verifiable (tests pass), with controllable risk. Rewriting carries much higher risk, especially without test coverage. Recommendation: prefer refactoring over rewriting unless the code is completely unmaintainable.

When should you refactor?

Three scenarios: ① Before adding a new feature — refactor the affected code first to make the new feature easier to add (the Boy Scout Rule: leave the camp cleaner than you found it); ② When fixing a bug — if the bug was caused by hard-to-understand code, refactor first, then fix; ③ When you spot code smells — duplicated code, long functions, large classes, excessive coupling. Do not schedule a "refactoring month" — refactoring should be part of daily development.

What if there are no tests?

Write tests first, then refactor. Refactoring without tests is gambling. Approach: ① Write characterization tests to lock in current behavior — assert the current output regardless of whether it is correct; ② Refactor, ensuring the output stays the same; ③ If you find a bug, fix it first, then refactor.

How do you break down a large refactoring?

Large refactorings must be broken into small steps. Each step must leave the code runnable and testable. Recommended approaches: ① By module — refactor one module at a time, verify, then continue; ② By layer — refactor the test layer first, then the interface layer, then the implementation layer; ③ Use the Strangler Fig pattern — write new code for new features, gradually migrate callers, then delete the old code.

This article comes from AI Enable Harness front-line delivery practice. Need a similar system or optimization service?

📡 Also published on: CSDN 知乎

Subscribe to Updates

Get notified when new articles are published. No spam, occasional updates only.

Subscribe →