Refactoring Patterns — Clean Code

Refactoring patterns adalah katalog teknik yang sudah terbukti untuk memperbaiki struktur kode tanpa mengubah behavior. Martin Fowler mendokumentasikan lebih da

Refactoring patterns adalah katalog teknik yang sudah terbukti untuk memperbaiki struktur kode tanpa mengubah behavior. Martin Fowler mendokumentasikan lebih dari 70 pattern di bukunya "Refactoring".

Pattern 1: Extract Function Gunakan saat ada blok kode yang perlu komentar, atau kode yang sama diulang.

// SEBELUM
function printOwing(invoice) {
  let outstanding = 0;
  console.log("***********************");
  console.log("**** Customer Owes ****");
  console.log("***********************");

  for (const o of invoice.orders) {
    outstanding += o.amount;
  }

  const today = new Date();
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
  console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

// SESUDAH
function printOwing(invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  recordDueDate(invoice);
  printDetails(invoice, outstanding);
}

function printBanner() {
  console.log("***********************");
  console.log("**** Customer Owes ****");
  console.log("***********************");
}

function calculateOutstanding(invoice) {
  return invoice.orders.reduce((sum, o) => sum + o.amount, 0);
}

function recordDueDate(invoice) {
  const today = new Date();
  invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}

function printDetails(invoice, outstanding) {
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
  console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

Pattern 2: Replace Conditional with Polymorphism Gunakan saat ada switch/if-else berdasarkan tipe objek.

// SEBELUM
function getSpeed(bird) {
  switch (bird.type) {
    case "EuropeanSwallow": return 35;
    case "AfricanSwallow": return bird.numberOfCoconuts > 2 ? 0 : 40;
    case "NorwegianParrot": return bird.isNailed ? 0 : 18 + bird.voltage / 10;
    default: throw new Error(`Unknown bird: ${bird.type}`);
  }
}

// SESUDAH
class EuropeanSwallow {
  get speed() { return 35; }
}

class AfricanSwallow {
  constructor(numberOfCoconuts) { this.numberOfCoconuts = numberOfCoconuts; }
  get speed() { return this.numberOfCoconuts > 2 ? 0 : 40; }
}

class NorwegianParrot {
  constructor(voltage, isNailed) { this.voltage = voltage; this.isNailed = isNailed; }
  get speed() { return this.isNailed ? 0 : 18 + this.voltage / 10; }
}

Pattern 3: Introduce Parameter Object Gunakan saat banyak parameter yang selalu bersama.

// SEBELUM: 5 parameter
function createReport(startDate, endDate, minAmount, maxAmount, userId) { ... }
createReport("2024-01-01", "2024-12-31", 0, 1000000, 42);

// SESUDAH: 1 parameter object
function createReport(criteria) { ... }
createReport({
  dateRange: { start: "2024-01-01", end: "2024-12-31" },
  amountRange: { min: 0, max: 1000000 },
  userId: 42,
});

Pattern 4: Move Function Gunakan saat fungsi lebih sering menggunakan data dari class/module lain.

// SEBELUM: accountType ada di Account, tapi fungsi ada di Customer
class Customer {
  getDiscount() {
    return this.account.accountType === "premium" ? 0.2 : 0.1;
  }
}

// SESUDAH: pindahkan ke Account
class Account {
  getDiscount() {
    return this.accountType === "premium" ? 0.2 : 0.1;
  }
}
class Customer {
  getDiscount() { return this.account.getDiscount(); }
}

Pattern 5: Replace Temp with Query Gunakan saat variable temporary menampung hasil ekspresi yang bisa dijadikan fungsi.

// SEBELUM
function calculateTotal(order) {
  const basePrice = order.quantity * order.itemPrice;
  const discount = basePrice > 1000 ? 0.05 : 0;
  return basePrice * (1 - discount);
}

// SESUDAH
function basePrice(order) { return order.quantity * order.itemPrice; }
function discountFactor(order) { return basePrice(order) > 1000 ? 0.05 : 0; }
function calculateTotal(order) { return basePrice(order) * (1 - discountFactor(order)); }

Workflow refactoring yang aman:

  1. Tulis test sebelum refactor (atau pastikan test sudah ada)
  2. Satu perubahan kecil per langkah
  3. Run test setelah setiap langkah
  4. Commit setelah setiap refactoring yang sukses
  5. Jangan refactor dan tambah fitur dalam commit yang sama

🎭 Analogi sehari-hari

Refactoring patterns itu kayak resep katalog koki profesional. Cara goreng telur ada nama: omelet, scrambled, sunny side up, poached. Tiap teknik dipake di kondisi tertentu, dengan langkah yang udah teruji puluhan tahun. Kamu gak perlu nemuin sendiri "cara goreng telur yang benar" — ikutin resep yang udah ada, hasil predictable, gak gosong. Refactoring patterns sama: 70+ teknik dari Martin Fowler dengan nama spesifik (Extract Function, Replace Conditional with Polymorphism, Introduce Parameter Object). Pas ngeliat kode jelek, kamu PUNYA vocabulary buat ngomong "ini perlu Extract Function" alih-alih "ini perlu dirapihin entah gimana caranya".

⚠️ Jebakan yang sering ditemui

Pattern Paling Sering Dipake

  1. Extract Function — pecah blok kode ke fungsi baru
  2. Inline Function — hapus fungsi yang gak nambah value
  3. Extract Variable — ekspresi kompleks → variable bernama
  4. Inline Variable — hapus variable yang trivial (const x = 5; return xreturn 5)
  5. Rename Variable/Function — kasih nama lebih jelas
  6. Replace Magic Number with Constant429HTTP_TOO_MANY_REQUESTS
  7. Decompose Conditional — kondisi kompleks → fungsi terpisah
  8. Replace Loop with Pipelinefor + pushmap/filter/reduce
  9. Replace Conditional with Polymorphism — banyak switch (type) → polymorphism
  10. Introduce Parameter Object — fungsi 5+ param → bungkus jadi object
  11. Move Function — pindahin fungsi ke class/module yang lebih cocok
  12. Replace Temp with Query — variable temp → fungsi yang return value

Workflow Refactor Aman (Recap)

  1. Tulis test — safety net, must-have sebelum refactor
  2. Satu pattern per langkah — Extract Function, commit. Lalu Rename, commit. Bukan combine.
  3. Run test setelah tiap langkah — ngga lewatin step ini
  4. Commit per langkah — git history clean, rollback gampang kalo break
  5. Pisah refactor commit dari feature commit — review-able

🎯 Pilih pattern apa untuk masalah apa?

  • Blok kode panjang dengan komentar "// step 1, 2, 3" → Extract Function (per step)
  • Variable trivial yang gak nambah value → Inline Variable
  • Ekspresi kompleks dipake berulang → Extract Variable + nama jelas
  • Magic number/string → Replace with Named Constant
  • Conditional 5+ branches berdasar type → Replace with Polymorphism
  • Loop manual yang transform array → Replace with Pipeline (map/filter/reduce)
  • Fungsi dengan 4+ parameter → Introduce Parameter Object
  • Method di class yang gak fit → Move Function/Method
  • Class God Object → Extract Class (pecah jadi multiple class)

Aturan: pilih pattern yang nge-target masalah SPESIFIK kamu. Jangan apply pattern karena "kerennya".

TL;DR: Refactoring patterns = katalog teknik proven (Martin Fowler 70+ patterns) buat improve kode tanpa ubah behavior. Vocabulary spesifik (Extract Function, Replace Conditional with Polymorphism) > "rapihin entah gimana". Top 5 paling sering: Extract Function, Rename, Extract Variable, Replace Magic Number, Decompose Conditional. Apply ONE pattern per commit, run test after each, pisah dari feature commit. Test = safety net wajib.