SOLID Principles: 5 Pilar Clean Code yang Harus Kamu Kenal

Kalau kamu sering baca buku Clean Architecture atau Clean Code karya Robert C. Martin (Uncle Bob),
pasti pernah dengar istilah SOLID Principles.

Banyak yang menganggap SOLID itu teori berat yang cuma relevan buat project besar —
padahal justru konsep ini yang bikin kode kecil pun terasa profesional dan terstruktur.


💡 Apa Itu SOLID?

SOLID adalah singkatan dari lima prinsip desain yang membantu kita menulis kode yang:

  • Mudah dibaca

  • Mudah diubah

  • dan Lebih tahan terhadap perubahan

Istilah ini pertama kali diperkenalkan oleh Robert C. Martin (Uncle Bob) — seorang legend di dunia software engineering.

Berikut singkatannya:

Huruf

Prinsip

Arti Singkat

S

Single Responsibility Principle

Satu kelas / fungsi = satu tanggung jawab

O

Open/Closed Principle

Kode terbuka untuk ditambah, tapi tertutup untuk diubah

L

Liskov Substitution Principle

Subclass harus bisa menggantikan parent class tanpa masalah

I

Interface Segregation Principle

Gunakan interface yang spesifik, jangan terlalu umum

D

Dependency Inversion Principle

Ketergantungan pada abstraksi, bukan implementasi

🧩 Mari Kita Bahas Satu per Satu


1️⃣ Single Responsibility Principle (SRP)

Satu fungsi, satu alasan untuk berubah.

Artinya: setiap class atau fungsi harus fokus pada satu tugas utama.
Kalau satu class ngurus banyak hal, maka perubahan di satu bagian bisa merusak bagian lainnya.

❌ Contoh yang Melanggar:

class User {
  constructor(public name: string, public email: string) {}

  saveToDatabase() {
    console.log(`Saving ${this.name}...`);
  }

  sendWelcomeEmail() {
    console.log(`Sending email to ${this.email}...`);
  }
}

Kelas User di sini melakukan dua hal: menyimpan data dan mengirim email — dua tanggung jawab berbeda.

✅ Versi Clean:

class User {
  constructor(public name: string, public email: string) {}
}

class UserRepository {
  save(user: User) {
    console.log(`Saving ${user.name}...`);
  }
}

class EmailService {
  sendWelcome(user: User) {
    console.log(`Sending email to ${user.email}...`);
  }
}

Sekarang setiap class hanya punya satu alasan untuk berubah:

  • User → menyimpan data user

  • UserRepository → menyimpan ke database

  • EmailService → mengirim email

💡 Manfaatnya:
Kode lebih modular, gampang diuji, dan bisa dikembangkan tanpa saling ganggu.


2️⃣ Open/Closed Principle (OCP)

Kode sebaiknya bisa dikembangkan tanpa perlu diubah.

Bayangkan kamu punya sistem pembayaran:

function pay(method, amount) {
  if (method === "credit") creditPayment(amount);
  else if (method === "paypal") paypalPayment(amount);
}

Kalau besok nambah metode baru (misalnya “crypto”),
kamu harus ubah fungsi pay — melanggar OCP.

Versi Clean:

class Payment {
  pay(amount) {}
}

class CreditPayment extends Payment {
  pay(amount) { /* ... */ }
}

class PaypalPayment extends Payment {
  pay(amount) { /* ... */ }
}

function processPayment(payment, amount) {
  payment.pay(amount);
}

Kamu bisa menambahkan CryptoPayment tanpa menyentuh kode lama.
Inilah esensi “terbuka untuk diperluas, tertutup untuk diubah”.


3️⃣ Liskov Substitution Principle (LSP)

Subclass harus bisa menggantikan parent class tanpa merusak perilaku.

Contoh klasik:

class Bird {
  fly() { console.log("Terbang"); }
}

class Penguin extends Bird {}

Kalau kamu panggil penguin.fly(), hasilnya aneh — karena penguin gak bisa terbang 🐧.
Artinya Penguin tidak bisa menggantikan Bird dengan benar.

Solusinya, ubah arsitektur:

class Bird {}
class FlyingBird extends Bird {
  fly() {}
}
class Penguin extends Bird {
  swim() {}
}

Sekarang struktur class lebih masuk akal.
Penguin tetap “Bird”, tapi tidak dipaksa punya fly().


4️⃣ Interface Segregation Principle (ISP)

Lebih baik banyak interface kecil daripada satu interface besar.

Misal kamu punya interface:

interface Machine {
  print();
  scan();
  fax();
}

Tapi OldPrinter cuma bisa print().
Maka class itu terpaksa implementasi method yang gak perlu.

Lebih baik pisah:

interface Printer { print(); }
interface Scanner { scan(); }

class OldPrinter implements Printer {
  print() { /* ... */ }
}

Kamu cukup implement yang relevan — kode jadi ringan dan fleksibel.


5️⃣ Dependency Inversion Principle (DIP)

Bergantunglah pada abstraksi, bukan implementasi.

💡 Analogi Sederhana:
Bayangkan kamu butuh motor untuk ke kantor.

  • Tanpa DI → kamu bikin motor sendiri tiap pagi.

  • Dengan DI → motor sudah disiapkan orang lain, kamu tinggal pakai.


⚙️ Tanpa Dependency Injection (buruk):

class UserService {
  constructor() {
    this.db = new Database(); // tightly coupled
  }

  getUser(id) {
    return this.db.query(`SELECT * FROM users WHERE id=${id}`);
  }
}

class Database {
  query(sql) {
    console.log('Executing:', sql);
  }
}

🧱 Masalah:

  • UserService terlalu bergantung pada Database.

  • Kalau nanti mau ganti ke MongoDB, harus ubah kode di UserService.

  • Susah di-test karena gak bisa mock database-nya.


Dengan Dependency Injection (baik):

class UserService {
  constructor(db) {
    this.db = db; // dependency injected
  }

  getUser(id) {
    return this.db.query(`SELECT * FROM users WHERE id=${id}`);
  }
}

class Database {
  query(sql) {
    console.log('Executing:', sql);
  }
}

// dependency di-inject dari luar
const db = new Database();
const userService = new UserService(db);
userService.getUser(1);

💡 Hasilnya:

  • UserService gak peduli detail Database.

  • Gampang diganti atau di-mock saat testing.

  • Kode jadi fleksibel dan scalable.


🧠 Pendapat Saya

Menurut saya, SOLID itu bukan sekadar teori OOP, tapi cara berpikir.
Bahkan di React atau JavaScript functional pun, prinsipnya tetap bisa diterapkan.

  • SRP → pecah komponen React jadi kecil dan fokus satu tugas.

  • OCP → gunakan props atau hooks agar mudah dikembangkan tanpa ubah struktur.

  • DIP → pisahkan logic dan UI, jangan campur API call langsung di komponen.

Prinsipnya: buat kode yang bisa hidup lama tanpa bikin frustrasi.


🏁 Kesimpulan

Clean code bukan tentang “mengingat lima singkatan”,
tapi tentang membangun pola pikir untuk menjaga kode tetap sehat.

  • SRP → Fokus pada satu tanggung jawab

  • OCP → Jangan ubah kode lama tanpa alasan kuat

  • LSP → Struktur class dengan logika yang masuk akal

  • ISP → Gunakan interface yang spesifik

  • DIP → Hindari ketergantungan pada implementasi detail

Semakin kamu paham prinsip ini, semakin kamu sadar bahwa kode bagus itu bukan yang rumit, tapi yang mudah dimengerti.

Hey there 👋

Ready to help you explore?