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;
}
VOUCHERS[voucherCode]mengambil tarif dari tabel. Kode yang gak ada di tabel, termasuk'', menghasilkanundefined.?? 0menggantiundefineddengan 0, jadi kode yang gak dikenal tetap tanpa potongan, persis seperti sebelumnya. Hasilnya tetap; perilakunya gak diubah.- Kunci tabel harus sama persis:
{ hemat10: 0.1 }dengan huruf kecil bikin A + HEMAT10 dan DBEDA. - Voucher baru nanti tinggal satu baris di tabel. Tapi menambah voucher adalah fitur, jadi dikerjakan setelah refactor selesai, di commit terpisah.
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
- Refactor sekaligus menambah fitur. Kalau hasil berubah, kamu gak tahu yang mana penyebabnya. Refactor dulu sampai keempat keranjang
OK, commit, baru tambah fiturnya. - Langkah besar. Enam teknik sekaligus, lalu ada
BEDA: yang mana? Satu langkah, satu cek. - Refactor tanpa test. Tanpa empat keranjang, nol yang hilang di
20000baru ketahuan dari komplain pembeli. - Test yang gak menyentuh batasnya. Keempat keranjang gak ada yang tepat Rp 200.000, jadi
<yang berubah jadi<=lolos semua. Tambahkan keranjang di angka batas. - Di latihan, menyisakan
else if. Rantai kondisinya harus hilang semua; tabel yang mengerjakannya.
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.