Code smell adalah tanda bahwa ada yang perlu diperbaiki di kode — bukan bug, tapi indikasi desain kurang baik. Istilah ini dipopulerkan Kent Beck dan dibukukan Martin Fowler di Refactoring.
Smell bukan larangan mutlak — ia undangan untuk berpikir: "Apakah ada cara yang lebih baik?" Kadang jawabannya "tidak, ini sudah sesuai konteks". Tapi lebih sering, smell menunjukkan hutang teknis yang sebaiknya dilunasi.
Mengapa hafal katalognya? Saat kamu tahu nama sebuah smell, kamu bisa:
- Berkomunikasi dengan tim ("ini Feature Envy")
- Menemukan refactoring yang tepat lebih cepat
- Review kode dengan lebih tajam
Katalog lengkap (18 smell klasik):
| # | Smell | Ciri | Refactoring |
|---|---|---|---|
| 1 | Duplicate Code | Kode/logika sama di beberapa tempat | Extract Function, Extract Class |
| 2 | Long Method | Fungsi terlalu panjang (> 20 baris) | Extract Function, Decompose Conditional |
| 3 | Large Class | Class terlalu banyak tanggung jawab | Extract Class, Extract Subclass |
| 4 | Long Parameter List | > 3–4 parameter | Introduce Parameter Object, Preserve Whole Object |
| 5 | Divergent Change | Satu class berubah karena banyak alasan berbeda | Split Class |
| 6 | Shotgun Surgery | Satu perubahan butuh edit di banyak class | Move Method, Inline Class |
| 7 | Feature Envy | Method di class A terlalu sering akses data class B | Move Method ke class B |
| 8 | Data Clumps | Sekelompok field yang selalu muncul bersama | Extract Class, Introduce Parameter Object |
| 9 | Primitive Obsession | String/number untuk konsep domain (uang, email, tanggal) | Replace Primitive with Object (Value Object) |
| 10 | Switch Statements | switch/if panjang berdasarkan tipe |
Replace Conditional with Polymorphism |
| 11 | Parallel Inheritance | Tambah subclass X → harus tambah subclass Y | Move Method, Move Field |
| 12 | Lazy Class | Class yang hampir tidak berguna | Inline Class |
| 13 | Speculative Generality | Abstraksi untuk kebutuhan masa depan yang belum ada | Collapse Hierarchy, Inline Class |
| 14 | Temporary Field | Field yang hanya terisi di kasus tertentu | Extract Class, Introduce Null Object |
| 15 | Message Chains | a.b().c().d().e() — rantai panjang |
Hide Delegate (lihat Law of Demeter) |
| 16 | Middle Man | Class yang hanya mendelegasikan ke class lain | Remove Middle Man, Inline Method |
| 17 | Refused Bequest | Subclass menolak/override kebanyakan method parent | Replace Inheritance with Delegation |
| 18 | Comments | Komentar banyak = kode tidak self-explanatory | Extract Function, Rename Variable |
Contoh: Primitive Obsession
// BAU: uang direpresentasikan sebagai angka biasa
function charge(amount, currency) {
if (currency !== "IDR") throw new Error("only IDR");
return amount * 1.11; // PPN 11%
}
// BERSIH: Money sebagai Value Object
class Money {
constructor(amount, currency = "IDR") {
this.amount = amount;
this.currency = currency;
}
add(other) {
if (this.currency !== other.currency) throw new Error("currency mismatch");
return new Money(this.amount + other.amount, this.currency);
}
withTax(rate) {
return new Money(this.amount * (1 + rate), this.currency);
}
}
Contoh: Feature Envy
// BAU: Order ngintip terlalu dalam ke Customer
class Order {
calculateDiscount(customer) {
if (customer.membership === "gold" && customer.yearsActive > 5) {
return this.total * 0.2;
}
return 0;
}
}
// BERSIH: biarkan Customer yang tahu dirinya sendiri
class Customer {
isLoyalGoldMember() {
return this.membership === "gold" && this.yearsActive > 5;
}
}
class Order {
calculateDiscount(customer) {
return customer.isLoyalGoldMember() ? this.total * 0.2 : 0;
}
}
Cara pakai katalog dalam review:
- Scan perubahan — apakah ada pola familiar dari tabel di atas?
- Tulis komentar review dengan nama smell-nya: "Bagian ini terlihat seperti Shotgun Surgery — kalau kita tambah status baru, kita harus edit 4 file. Bisa kita pertimbangkan Move Method?"
- Sarankan refactoring kolom kanan tabel — bukan perintah, tapi alternatif.
Peringatan: Smell bukan absolut. Switch Statements bisa saja tepat untuk parser sederhana. Long Method boleh kalau memang sekuens linier yang jelas. Kriteria akhir: apakah kode ini mudah diubah besok?
🎭 Analogi sehari-hari
Code smells itu kayak bau aneh di dapur. Bau bawang busuk, asap dari kompor mati, drainase mampet — bau-bau ini gak otomatis berarti masakan rusak, tapi PERINGATAN buat investigasi. Mungkin emang baru bersih-bersih bawang (legitimate), atau mungkin ada bawang busuk di pojokan yang udah lama (perlu fix). Code smells sama: nama-nama signal pattern yang mungkin bermasalah. Long Method, God Class, Shotgun Surgery, Feature Envy — vocabulary yang bikin diskusi review jadi spesifik. "Ini smell-nya Long Method, refactor pakai Extract Function?" lebih actionable dari "Ini gak rapi". Tapi awas: smell bisa false positive — long method memang OK kalau sequential simple.
⚠️ Jebakan yang sering ditemui
- Treat smell sebagai aturan absolut — auto refactor tiap "smell" detected = over-engineering. Smell = signal investigasi, bukan vonis.
- Refactor smell tanpa mikir cost-benefit — fix smell yang gak nimbulin pain = waste of time.
- Comment review pakai jargon doang — "ini smells like Feature Envy" tanpa explain = gak bantu junior. Jelasin + kasih solusi.
- Skip smell di prototype — OK di prototype. WAJIB diperhatikan di production code.
- Smell tools (linter) trust 100% — linter catch syntactic smell, miss semantic smell (Feature Envy, Inappropriate Intimacy).
- Pikir smell-free = good code — bisa "gak smell" tapi fundamentally salah designnya (logic, scope, abstraction).
Top 10 Smells Paling Sering
- Long Method — fungsi 50+ baris (Extract Function)
- Large Class — God Object 500+ baris (Extract Class)
- Long Parameter List — 4+ params (Introduce Parameter Object)
- Duplicated Code — kode sama di banyak tempat (Extract Function)
- Shotgun Surgery — 1 perubahan butuh edit banyak file (Move Method, consolidate)
- Feature Envy — method lebih banyak akses class lain (Move Method)
- Data Clumps — group fields yang selalu bareng (Extract Class)
- Primitive Obsession — terlalu banyak primitive
string/int(Replace with Object) - Switch Statements — banyak switch berdasar type (Replace with Polymorphism)
- Speculative Generality — abstraksi "siapa tau perlu nanti" yang gak pernah dipake (Inline / Hapus)
Cara Detect Smell
Manual — code review, pair programming, fresh eyes Static analysis tools — ESLint/SonarQube/PMD detect long method, complex condition, duplicated code Metrics — cyclomatic complexity, lines of code, coupling/cohesion ratio Smell test — "kalau saya kasih kode ini ke junior, mereka bingung gak?"
🎯 Smell terdeteksi — refactor sekarang atau biarin?
- Smell + ada bug rate tinggi area itu → refactor SEKARANG
- Smell + bakal ditambahin fitur → refactor DULU sebelum tambah fitur
- Smell di kode legacy yang stable → biarin, gak worth resiko
- Smell + clear better solution → refactor (improve)
- Smell tapi gak yakin solusinya → diskusi tim dulu, jangan buru-buru
- Smell tapi WORK ABOVE all (kode "nak dapur", legitimate context) → biarin
- Smell di prototype 1-week throwaway → biarin
Aturan: smell = signal investigasi. Decision refactor based on cost (effort + risk) vs benefit (maintainability + future ease).
TL;DR: Code smells = vocabulary buat pattern yang MUNGKIN problematic. Bukan vonis, signal investigasi. Top: Long Method, God Class, Shotgun Surgery, Feature Envy, Duplicated Code. Detect via review, static analysis, metrics. Pakai vocabulary spesifik di review ("smells like Feature Envy") biar diskusi actionable. Refactor smell yang IMPACT (bug rate, blocking feature), biarin yang gak nimbulin pain. Smell-free ≠ good code (semantic correctness > syntactic cleanliness).