Skip to content

Buổi 07: Refactoring Mastery — Clean Code Có Hệ Thống 🔧

Thành quả: Refactor 1 legacy module: extract functions, rename, decouple, add tests


🎯 Mục Tiêu

  1. Nhận diện 10 code smells phổ biến nhất
  2. Master 8 refactoring techniques an toàn
  3. Dùng cm-clean-code + cm-debugging workflow
  4. Refactor có safety net: test trước → refactor → verify
  5. Hiểu khi nào NÊN và KHÔNG NÊN refactor

📖 Phần 1: 10 Code Smells

#SmellVí dụSeverity
1God FunctionFunction > 50 lines🔴 Critical
2Magic Numbersif (status === 3)🟡 Medium
3Dead CodeFunctions never called🟡 Medium
4Duplicated LogicSame code in 3+ places🔴 Critical
5Deep Nestingif inside if inside if (>3 levels)🟡 Medium
6Long Parameter Listfunction(a, b, c, d, e, f, g)🟡 Medium
7Feature EnvyClass A dùng data của Class B quá nhiều🟠 High
8Shotgun Surgery1 change affects 10+ files🔴 Critical
9Primitive ObsessionDùng string thay vì enum/type🟡 Medium
10Inconsistent NaminggetUserData vs fetchUser vs loadUserInfo🟡 Medium

Phát hiện bằng AI

bash
# Scan for code smells
cat src/services/order.service.ts | gemini "Analyze this code for:
1. Code smells (God function, duplication, deep nesting)
2. Naming inconsistencies
3. Missing error handling
4. Performance issues
5. Security vulnerabilities
Rate each issue: 🔴 Critical, 🟠 High, 🟡 Medium, 🟢 Low"

📖 Phần 2: 8 Refactoring Techniques

1. Extract Function

javascript
// ❌ BEFORE: God function
async function processOrder(order) {
  // 50 lines of validation, calculation, notification, logging...
  const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
  const tax = total * 0.1;
  const shipping = total > 100 ? 0 : 10;
  // ... 30 more lines
}

// ✅ AFTER: Small, focused functions
async function processOrder(order) {
  const total = calculateTotal(order.items);
  const tax = calculateTax(total);
  const shipping = calculateShipping(total);
  await saveOrder({ ...order, total, tax, shipping });
  await notifyCustomer(order.customerId);
}

function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
function calculateTax(total) { return total * 0.1; }
function calculateShipping(total) { return total > 100 ? 0 : 10; }

2. Replace Magic Numbers

javascript
// ❌ BEFORE
if (user.role === 3) { /* admin logic */ }
if (order.status === 5) { /* completed */ }

// ✅ AFTER
const UserRole = { ADMIN: 3, USER: 1, MODERATOR: 2 } as const;
const OrderStatus = { PENDING: 1, PROCESSING: 2, SHIPPED: 4, COMPLETED: 5 } as const;

if (user.role === UserRole.ADMIN) { /* admin logic */ }
if (order.status === OrderStatus.COMPLETED) { /* completed */ }

3. Guard Clause (Flatten Nesting)

javascript
// ❌ BEFORE: Deep nesting
function getDiscount(user, order) {
  if (user) {
    if (user.isPremium) {
      if (order.total > 100) {
        return 0.2;
      } else {
        return 0.1;
      }
    } else {
      return 0;
    }
  }
  return 0;
}

// ✅ AFTER: Guard clauses (early return)
function getDiscount(user, order) {
  if (!user) return 0;
  if (!user.isPremium) return 0;
  if (order.total > 100) return 0.2;
  return 0.1;
}

4. Extract Interface/Type

typescript
// ❌ BEFORE: Primitive obsession
function createUser(name: string, email: string, age: number, role: string)

// ✅ AFTER: Type-safe
interface CreateUserInput {
  name: string;
  email: string;
  age: number;
  role: 'admin' | 'user' | 'moderator';
}
function createUser(input: CreateUserInput)

5-8: More Techniques

TechniqueWhenBefore → After
Replace Conditional with Polymorphismswitch/if-else on typeStrategy Pattern
Introduce Parameter Object4+ params{ config } object
Move Logic to ServiceFat controllerController → Service → Repository
Decompose ConditionalComplex if conditionsNamed boolean helpers

📖 Phần 3: Safe Refactoring Workflow

The Golden Rule: Test → Refactor → Verify

Step 1: CHARACTERIZE
  → Run existing tests (if any)
  → Add characterization tests cho code cần refactor
  → Tests capture CURRENT behavior (even if buggy)

Step 2: REFACTOR
  → Apply 1 technique at a time
  → Small changes, frequent commits
  → "Make the change easy, then make the easy change"

Step 3: VERIFY
  → Run ALL tests after each change
  → If tests fail → revert immediately
  → git diff → review changes are correct

Step 4: COMMIT
  → Small, atomic commits
  → refactor(scope): description of what changed

cm-clean-code + cm-debugging Combo

cm-clean-code:
1. Detect dead code, duplicates, naming mess
2. TRIZ-powered suggestions
3. Run AFTER features, BEFORE PRs

cm-debugging:
1. If refactoring breaks something
2. 5-phase investigation: Reproduce → Isolate → Root cause → Fix → Verify
3. Never guess — measure

📖 Phần 4: Khi Nào KHÔNG Refactor

SituationDecision
Code sắp bị thay thế❌ Don't refactor
Không có tests và không có time viết⚠️ Risky — viết test trước
Deadline ngày mai❌ Ship first, refactor ticket later
"Code xấu nhưng đang hoạt động tốt"🤔 Add to tech debt backlog
"Tôi muốn code đẹp hơn" → no user value❌ Focus on features
Code gây bugs lặp lại✅ MUST refactor
Code block new features✅ MUST refactor
Code violates security✅ MUST refactor immediately

🧪 Lab: Refactor Legacy Module

Task: Refactor God Function (45 min)

Cho file legacy-order.js:

javascript
// legacy-order.js — THE GOD FUNCTION
async function handleOrder(req, res) {
  try {
    if (!req.body.items || req.body.items.length === 0) {
      return res.status(400).json({ error: 'No items' });
    }
    if (!req.body.customerId) {
      return res.status(400).json({ error: 'No customer' });
    }
    let total = 0;
    for (let i = 0; i < req.body.items.length; i++) {
      if (req.body.items[i].quantity < 1) {
        return res.status(400).json({ error: 'Bad quantity' });
      }
      total += req.body.items[i].price * req.body.items[i].quantity;
    }
    if (total > 500) {
      total = total * 0.9; // 10% discount
    }
    const tax = total * 0.1;
    const shipping = total > 100 ? 0 : 15;
    const finalTotal = total + tax + shipping;
    // Save to database...
    // Send email...
    // Log analytics...
    res.json({ orderId: '123', total: finalTotal });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
}

Steps:

  1. Write characterization tests
  2. Extract: validateOrder(), calculateTotal(), calculateDiscount(), calculateTax(), calculateShipping()
  3. Replace magic numbers
  4. Run tests after EACH extraction
  5. Commit with proper messages

🎓 Tóm Tắt

ConceptKey Takeaway
Code SmellsGod function, magic numbers, dead code, duplication
Safe RefactoringTest → Refactor → Verify → Commit
8 TechniquesExtract, guard clause, constants, type, decompose
When NOT toNo tests, deadline, code being replaced

⏭️ Buổi tiếp theo

Buổi 08: Test-Driven Development — Code Tự Tin 100% 🧪

Powered by CodyMaster × VitePress