Code Smells Catalog — Clean Code

Code smell adalah tanda bahwa ada yang perlu diperbaiki di kode — bukan bug, tapi indikasi desain kurang baik. Istilah ini dipopulerkan Kent Beck dan…

Code smell adalah tanda di permukaan kode yang biasanya menunjuk ke masalah lebih dalam: kode yang mahal diubah. Istilahnya dipopulerkan Kent Beck dan Martin Fowler di bab "Bad Smells in Code" buku Refactoring (1999). Bayangin bau aneh dari kulkas. Bau itu belum bukti ada makanan basi, tapi cukup jadi alasan untuk membuka dan mengecek. Smell juga begitu: bukan bug, tapi undangan untuk melihat lebih dekat, dan tiap smell punya nama dan obat refactoring-nya sendiri.

🧪 Tebakan cepat: kasir-lama.js di bawah punya sebelas smell. Dari empat keranjang test (ditambah satu dengan POTONG25), berapa yang BEDA? Tebak dulu, jawabannya ada di bagian "Smell bukan bug".

kasir-lama.js

File ini ditemukan di folder lama Toko Kaos Raka, sebelum track ini dimulai. Masih dipakai, masih jalan:

const laporan = []; // pura-pura file laporan
const outbox = [];  // pura-pura kotak keluar email

function prosesPesanan(nama, email, alamat, hp, k, v) {
  // cek email
  if (email.indexOf('@') < 0) return 'Email salah';
  // hitung total
  let t = 0;
  for (let i = 0; i < k.length; i++) {
    t = t + k[i].price * k[i].qty;
  }
  if (v == 'HEMAT10') {
    t = t - t * 0.1;
  } else if (v == 'POTONG25') {
    t = t - 25000;
  }
  if (t < 200000) {
    t = t + 15000;
  }
  // kirim email
  outbox.push(email + ': Total Rp ' + t.toLocaleString('id-ID'));
  // simpan laporan
  laporan.push(nama + ', ' + alamat + ', ' + hp + ': Rp ' + t.toLocaleString('id-ID'));
  return 'Total: Rp ' + t.toLocaleString('id-ID');
}

function labelKirim(nama, alamat, hp) {
  return 'Kepada: ' + nama + ', ' + alamat + ' (' + hp + ')';
}

function hitungDiskonLama(t) {
  return t * 0.15;
}

Smell bukan bug

Jalankan keranjang test-nya lewat prosesPesanan:

OK   Total: Rp 220.500
OK   Total: Rp 245.000
OK   Total: Rp 90.000
OK   Total: Rp 204.000
OK   Total: Rp 65.000

Nol yang BEDA. Itu jawaban tebakannya. Hasilnya benar; yang bermasalah adalah biaya mengubahnya. Smell adalah indikasi, bukan bug, dan kadang bentuk itu tetap tepat untuk konteksnya: switch kecil di parser sederhana, atau fungsi panjang yang cuma urutan langkah lurus. Pertanyaan penentunya: apakah kode ini masih gampang diubah besok?

Katalog dari satu file

Sebelas smell di kasir-lama.js, masing-masing dengan perubahan yang jadi mahal dan obat dari lesson sebelumnya:

Smell Di kasir-lama.js Perubahan yang jadi mahal Obatnya
Long Function prosesPesanan, 22 baris tiap perubahan harus membaca semuanya Extract Function
Long Parameter List 6 parameter urutan gampang tertukar Introduce Parameter Object
Data Clumps nama, alamat, hp selalu bareng, di dua fungsi menambah kode pos mengubah tiap tanda tangan Introduce Parameter Object
Magic Number 0.1, 25000, 200000, 15000 ganti batas ongkir: angka yang mana? Replace Magic Number with Symbolic Constant
Duplicated Code toLocaleString('id-ID') 3 kali ganti format rupiah: 3 baris Extract Function (formatRupiah)
Repeated Switches if (v == 'HEMAT10') ... else if voucher baru mengedit fungsi ini Lookup table atau Polymorphism
Primitive Obsession email cuma string, dicek indexOf('@') aturan email berubah: cari semua pengeceknya Replace Primitive with Object
Divergent Change prosesPesanan berubah karena 5 alasan sapaan email membuka rumus harga Pecah per alasan (SRP)
Mysterious Name k, v, t tiap pembaca menebak Rename
Comments // cek email, // hitung total komentar menambal nama yang kurang Extract Function bernama
Dead Code hitungDiskonLama, gak pernah dipanggil dibaca dan dirawat tanpa guna Remove Dead Code

Satu efek samping juga ketahuan saat test: lima cek harga di atas mengirim 5 email dan menulis 5 baris laporan, persis gejala yang dibahas di lesson Single Responsibility.

Pasangan yang sering tertukar

Primitive Obsession dan Value Object

Email sebagai string berarti tiap tempat yang menerima email harus ingat mengeceknya. Replace Primitive with Object membuat value object: objek kecil yang membawa nilai dan aturannya sendiri.

class Email {
  constructor(text) {
    if (!text.includes('@')) throw new Error('Email tidak valid: ' + text);
    this.text = text;
  }
}
new Email('[email protected]');  // lolos
new Email('sari-mail.com');  // Error: Email tidak valid: sari-mail.com

Aturan email sekarang di satu tempat, dan fungsi yang menerima Email tahu isinya sudah dicek. Uang bisa diperlakukan sama: Rupiah dengan method format().

Mesin menemukan sebagian

Tiga smell bisa dihitung: jumlah parameter, angka di kode, jumlah baris. Detektor dari latihan, dijalankan ke ketiga fungsi kasir-lama.js:

prosesPesanan     ["long-parameter-list","magic-number","long-function"]
labelKirim        []
hitungDiskonLama  ["magic-number"]

Sisanya butuh manusia. labelKirim bersih menurut mesin, padahal nama, alamat, hp di situ adalah Data Clumps. Shotgun Surgery, Divergent Change dan Feature Envy cuma kelihatan kalau kamu bertanya "apa yang berubah bareng?". Bahkan 0.1 lolos, karena pola \b\d+\b memecahnya jadi 0 dan 1, dua angka yang sengaja diabaikan. Alat seperti ESLint menangkap yang bisa dihitung; review menangkap sisanya.

Latihan: detectSmells

detectSmells(fnString) menerima kode sebuah fungsi sebagai string dan mengembalikan array nama smell, dengan urutan ini:

  1. "long-parameter-list": lebih dari 3 parameter. Ambil isi kurung pertama setelah function dengan /function\s*\w*\s*\(([^)]*)\)/, pecah dengan split(','), buang yang kosong, hitung.
  2. "magic-number": ada angka selain 0 dan 1. fnString.match(/\b\d+\b/g) || [], lalu filter yang bukan '0' dan '1'.
  3. "long-function": lebih dari 20 baris, fnString.split('\n').length > 20.

Tiap yang ketemu di-push ke smells. Output-nya:

["long-parameter-list","magic-number"]
[]
["magic-number","long-function"]

fn1 punya 4 parameter dan 42; fn2 bersih; fn3 punya 28 baris dan 99. Fungsi lain dengan ciri sama harus dikenali juga: function f(a,b,c,d) { return 7; } dapat dua smell, function g(a) { return a + 1; } gak dapat apa-apa.

⚠️ Jebakan yang sering ketemu

TL;DR: Code smell adalah tanda kode yang mahal diubah, bukan bug (Kent Beck dan Martin Fowler, Refactoring, 1999): kasir-lama.js dengan sebelas smell tetap lolos kelima cek. Katalognya: Long Function dan Comments (Extract Function), Long Parameter List dan Data Clumps (Introduce Parameter Object), Magic Number (konstanta bernama), Duplicated Code (formatRupiah), Repeated Switches (tabel atau Polymorphism), Primitive Obsession (value object seperti Email), Divergent Change (pecah per alasan), Mysterious Name (Rename), Dead Code (hapus). Shotgun Surgery: satu perubahan menyentuh banyak tempat, misalnya format laporan di 6 file; Divergent Change: satu tempat berubah karena banyak alasan. Mesin cuma menemukan yang bisa dihitung. Di latihan: detectSmells mengembalikan long-parameter-list (> 3 parameter), magic-number (angka selain 0 dan 1) dan long-function (> 20 baris) dalam urutan itu, dengan output ["long-parameter-list","magic-number"], [], ["magic-number","long-function"].

Yang akan kamu pelajari