Refactoring Techniques — Clean Code

Refactoring adalah mengubah struktur internal kode tanpa mengubah behavior-nya. Tujuan: membuat kode lebih mudah dibaca, dipahami, dan di-maintain. Aturan…

Refactoring adalah mengubah struktur kode tanpa mengubah hasilnya, dalam langkah-langkah kecil yang punya nama. Tiap langkah dicek, baru lanjut ke langkah berikutnya. Bayangin merapikan gudang Toko Kaos Raka. Kamu gak membongkar semua rak dalam sehari. Kamu pindahkan satu rak, lalu coba ambil empat barang yang paling sering dipesan. Masih ketemu semua? Lanjut rak berikutnya. Ada yang hilang? Kamu tahu persis rak mana penyebabnya.

🧪 Tebakan cepat: di langkah Replace Magic Number kamu salah ketik const FREE_SHIPPING_MIN = 20000;, kurang satu nol. Dari empat keranjang test, berapa yang menangkapnya: nol, dua, atau empat? Tebak dulu, jawabannya ada di bagian "Rename dan Replace Magic Number".

Aturannya: langkah kecil, hasil tetap

"Barang yang paling sering dipesan" di kode kita adalah empat keranjang dari lesson pertama, dicek dengan fungsi check:

function check(actual, expected) {
  console.log(actual === expected ? 'OK   ' + actual : 'BEDA ' + actual + ', harusnya ' + expected);
}
check(calculateTotal(cartA, 'HEMAT10'), 'Total: Rp 220.500');
check(calculateTotal(cartA, ''), 'Total: Rp 245.000');
check(calculateTotal(cartC, ''), 'Total: Rp 90.000');
check(calculateTotal(cartD, 'HEMAT10'), 'Total: Rp 204.000');

Sebelum langkah pertama, keempatnya OK. Sesudah tiap langkah, keempatnya harus tetap OK. Kalau ada BEDA, yang salah pasti langkah terakhir, karena cuma itu yang berubah.

Enam langkah dari hitung(k, v)

Fungsi kasir yang berantakan dari lesson pertama, hitung(k, v), dirapikan dengan enam teknik bernama:

Langkah Teknik Yang berubah 4 keranjang
1 Rename hitung, k, v, t jadi calculateTotal, cart, voucherCode, total OK
2 Replace Magic Number with Symbolic Constant 0.1, 200000, 15000 jadi konstanta bernama OK
3 Extract Variable kondisi ongkir diberi nama needsShipping OK
4 Extract Function dipecah jadi calculateSubtotal, applyVoucher, addShipping, formatRupiah OK
5 Inline Variable needsShipping dilebur lagi ke addShipping OK
6 Replace Conditional with Lookup if (voucherCode === 'HEMAT10') jadi tabel VOUCHERS OK

Tiap baris OK artinya keempat keranjang dijalankan setelah langkah itu dan hasilnya sama. Bagian berikut menunjukkan tiap teknik, dan salah langkah yang paling sering terjadi di sana.

Rename dan Replace Magic Number

Rename mengganti nama, semua pemakaiannya sekaligus. Kalau satu t tertinggal di if (t < 200000), check pertama langsung berhenti dengan Uncaught ReferenceError: t is not defined.

Replace Magic Number with Symbolic Constant memberi nama ke angka ajaib:

const VOUCHER_DISCOUNT = 0.1;
const FREE_SHIPPING_MIN = 200000;
const SHIPPING_FEE = 15000;

Kalau salah ketik FREE_SHIPPING_MIN = 20000:

OK   Total: Rp 220.500
OK   Total: Rp 245.000
BEDA Total: Rp 75.000, harusnya Total: Rp 90.000
BEDA Total: Rp 189.000, harusnya Total: Rp 204.000

Dua keranjang menangkapnya, C dan D. Itu jawaban tebakannya. A (245.000 dan 220.500) di atas 200.000 maupun 20.000, jadi gak bisa membedakan; C dan D ada di bawah 200.000, jadi langsung ketahuan.

Extract Variable dan Inline Variable

Extract Variable memberi nama ke sebuah ekspresi supaya kodenya bisa dibaca seperti kalimat:

if (voucherCode === 'HEMAT10') total = total - total * VOUCHER_DISCOUNT;
const needsShipping = total < FREE_SHIPPING_MIN;
if (needsShipping) total = total + SHIPPING_FEE;

Contoh lain yang sering muncul: if (user.age >= 18 && user.hasID && !user.isBanned) jadi const isEligibleForAccess = user.age >= 18 && user.hasID && !user.isBanned;. Hati-hati menaruhnya: needsShipping yang dideklarasikan sebelum baris voucher menghitung dari total yang belum dipotong, dan keranjang D jadi Total: Rp 189.000.

Inline Variable kebalikannya: kalau namanya gak menambah arti, lebur lagi ke ekspresinya. Setelah langkah 4, needsShipping tinggal di dalam fungsi bernama addShipping, jadi if (amount < FREE_SHIPPING_MIN) return amount + SHIPPING_FEE; sudah cukup jelas. Extract dan Inline sama-sama refactor; yang dipilih tergantung mana yang lebih gampang dibaca di tempat itu.

Extract Function

Extract Function memindahkan satu blok ke fungsi bernama, seperti di lesson Fungsi Kecil & Fokus:

function calculateTotal(cart, voucherCode) {
  let total = calculateSubtotal(cart);
  total = applyVoucher(total, voucherCode);
  total = addShipping(total);
  return 'Total: ' + formatRupiah(total);
}

Salah langkah yang khas di sini: lupa mengoper parameter. applyVoucher(total) tanpa voucherCode bikin voucher gak pernah terpakai: A + HEMAT10 jadi Rp 245.000 dan D jadi Rp 210.000, dua BEDA.

Replace Conditional with Lookup

Kondisi yang memilih nilai berdasarkan sebuah kunci bisa diganti tabel (object), dan rantai if/else if hilang:

const VOUCHERS = { HEMAT10: 0.1 };

function applyVoucher(amount, voucherCode) {
  const rate = VOUCHERS[voucherCode] ?? 0;
  return amount - amount * rate;
}

Latihan: getDiscount

getDiscount(type, price) di latihan memakai rantai if/else if. Ganti dengan tabel DISCOUNT_RATES: regular 0, member 0.1, vip 0.2, employee 0.3. Fungsinya mengembalikan price * (1 - rate), dan tipe yang gak dikenal jatuh ke 0 lewat DISCOUNT_RATES[type] ?? 0 (atau || 0), tanpa satu pun else if. Output-nya:

100000
90000
80000
70000
100000

⚠️ Jebakan yang sering ketemu

TL;DR: Refactoring mengubah struktur tanpa mengubah hasil, dalam langkah kecil bernama yang tiap langkahnya dicek dengan empat keranjang. hitung(k, v) dirapikan lewat Rename, Replace Magic Number with Symbolic Constant (FREE_SHIPPING_MIN), Extract Variable (needsShipping, isEligibleForAccess), Extract Function, Inline Variable dan Replace Conditional with Lookup (VOUCHERS[voucherCode] ?? 0), dan keempat keranjang tetap OK di tiap langkah. Salah langkah langsung ketahuan di langkah itu: t yang tertinggal jadi ReferenceError, 20000 ditangkap C dan D, parameter yang lupa dioper ditangkap A + HEMAT10 dan D. Test cuma menangkap yang ia sentuh, jadi tambah keranjang di angka batas. Jangan campur refactor dengan fitur. Di latihan: tabel DISCOUNT_RATES dengan employee: 0.3, tanpa else if, output 100000, 90000, 80000, 70000, 100000.

Yang akan kamu pelajari