Halo teman-teman sobat Indonesia Belajar!
Selamat datang. Senang sekali bisa berbagi di sini. Seperti yang saya sampaikan di sesi live, saya sendiri masih dalam proses belajar mendalami Clean Code dan SOLID Principle. Ini bukan karena tuntutan akademis, tapi karena tuntutan kerja tim yang sangat nyata.
Di dunia profesional, kita tidak menulis kode hanya untuk diri sendiri atau agar programnya running. Kita menulis kode yang juga akan dibaca, dipahami, dan dikembangkan oleh orang lain. Kode yang kita tulis hari ini adalah warisan (atau beban) untuk rekan setim kita di masa depan.
Buku "Clean Code" dari Robert C. Martin (Uncle Bob) benar-benar membuka mata saya. Salah satu inti dari buku itu adalah SOLID Principle. Masalahnya, hampir semua contoh di luar sana menggunakan Java. Nah, di sini, saya akan merangkum materi kita menggunakan Python, berdasarkan studi kasus yang sudah kita bedah bersama.
Ini adalah dokumentasi perjalanan belajar saya, dan saya harap ini bisa jadi insight yang kuat buat teman-teman.
1. Fondasi Awal: "Membersihkan" Kode (Clean Code)
Sebelum kita bicara 5 prinsip SOLID, kita harus mulai dari yang paling dasar: Keterbacaan (Readability). Kode yang running bukan berarti kode yang maintenable (mudah dirawat).
Lihat kode awal kita:
Python
# Versi Awal (Berantakan)
class Order:
# ... (definisi kelas) ...
def total_price(self):
total = 0
for i in range(len(self.prices)):
total += self.quantities[i] * self.prices[i]
return total
def pay(self, payment_type, security_code):
if payment_type == "debit":
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
self.status = "paid"
elif payment_type == "credit":
# ... (logika kredit) ...
Ada dua masalah besar di sini:
- Inkonsistensi Tampilan: Penggunaan spasi, kutip satu (') dan kutip dua (") yang campur aduk. Ini sepele, tapi membuat mata lelah.
- Logika yang "Tidak Pythonic": Looping menggunakan range(len(...)) sangat tidak dianjurkan di Python. Ini menyulitkan pembacaan dan rawan bug (bayangkan jika quantities dan prices tidak sinkron).
Langkah 1: Gunakan Auto-Formatter (Black)
Ini adalah "sudut pandang" yang sering dilewatkan pemula: Jangan buang waktu Anda untuk memformat kode secara manual.
Kita sebagai manusia tidak konsisten. Biarkan mesin yang melakukannya. Di Python, tool terbaik untuk ini adalah Black, "The Unopinionated Code Formatter."
Bash
# Instalasi pip install black # Penggunaan black nama_file_kamu.py
Black akan secara otomatis mengubah semua kutip jadi " dan merapikan spasi. Ini membuat kode kita seragam di seluruh tim.
👉 Apa yang bisa kamu lakukan sekarang:
- Install black di virtual environment proyekmu.
- Jalankan black . di terminal pada root proyekmu.
- (Level Lanjut): Integrasikan Black dengan VS Code atau editor-mu agar berjalan otomatis setiap kali kamu menekan Save.
Langkah 2: Refactor ke Cara "Pythonic"
Kita ubah total_price dari menggunakan index menjadi menggunakan zip.
Python
# Versi Refactor (Pythonic)
def total_price(self):
total = 0
# zip menggabungkan dua list menjadi pasangan tuple
for qty, price in zip(self.quantities, self.prices):
total += qty * price
return total
Ini jauh lebih bersih, lebih aman, dan lebih mudah dibaca. Kita langsung menyatakan apa yang kita mau (kuantitas dan harga) bukan bagaimana caranya (pakai indeks i).
👉 Apa yang bisa kamu lakukan sekarang:
- Cari semua loop for i in range(len(nama_list)) di kodemu.
- Tanyakan pada dirimu: "Apakah saya benar-benar butuh indeksnya?"
- Jika tidak, ganti dengan for item in nama_list.
- Jika kamu perlu memproses dua list atau lebih secara paralel, gunakan zip().
2. Membedah 5 Prinsip SOLID
Sekarang kita masuk ke intinya. SOLID adalah akronim untuk 5 prinsip desain yang membuat kode kita fleksibel, mudah dirawat, dan mudah di-tes.
S - Single Responsibility Principle (SRP)
Prinsip Tanggung Jawab Tunggal
- Konsep Inti: Sebuah kelas hanya boleh punya satu alasan untuk berubah.
- Studi Kasus (Masalah): Kelas Order kita punya dua alasan untuk berubah:
- Jika aturan penambahan item berubah (misal, tambah diskon per item).
- Jika metode pembayaran berubah (misal, tambah gopay, hapus debit).
- Sudut Pandang Saya: Kelas yang "gemuk" (melakukan terlalu banyak hal) adalah sumber utama bug dan sakit kepala. Saat kamu ingin mengubah A, tiba-tiba B rusak. SRP mencegah ini.
- Solusi: Kita PISAHKAN tanggung jawabnya.
- Biarkan kelas Order hanya mengurus data order.
- Buat kelas baru: PaymentProcessor, yang khusus mengurus logika pembayaran.
Python
# Sesuai SRP
class Order:
# HANYA mengurus item, quantity, price, status
def add_item(self, name, quantity, price): ...
def total_price(self): ...
class PaymentProcessor:
# HANYA mengurus pembayaran
def pay_debit(self, order, security_code):
print("Processing debit payment type")
print(f"Verifying security code: {security_code}")
order.status = "paid"
def pay_credit(self, order, security_code):
# ... logika kredit
👉 Apa yang bisa kamu lakukan sekarang:
- Lihat kelas tergemuk di proyekmu.
- Tulis di kertas: "Apa saja tanggung jawab kelas ini?"
- Jika kamu menemukan kata "DAN" (misal: "mengurus user DAN mengirim email"), segera pisahkan menjadi dua kelas (cth: User dan EmailNotifier).
O - Open/Closed Principle (OCP)
Prinsip Terbuka/Tertutup
- Konsep Inti: Kode harus terbuka untuk ekstensi (ditambahi), tapi tertutup untuk modifikasi (diubah).
- Studi Kasus (Masalah): Di PaymentProcessor kita, jika kita ingin menambah metode gopay, kita harus mengedit kelas itu dan menambahkan def pay_gopay(...). Ini melanggar prinsip "tertutup untuk modifikasi".
- Sudut Pandang Saya: Ini adalah prinsip "anti-ngedit" kode yang sudah stabil. Mengedit kode yang sudah jalan itu berisiko menimbulkan bug baru di fitur yang lama.
- Solusi: Gunakan Abstraksi. Di Python, kita pakai ABC (Abstract Base Class).
- Kita buat "kontrak" (kelas abstrak) bernama PaymentProcessor.
- Kontrak ini bilang: "Siapapun yang mau jadi prosesor pembayaran, WAJIB punya method pay()."
- Kita buat kelas-kelas konkret (turunan) yang menepati janji itu.
Python
from abc import ABC, abstractmethod
# 1. Buat KONTRAK (Abstraksi)
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order, security_code):
pass
# 2. Buat implementasi KONKRET
class DebitPaymentProcessor(PaymentProcessor):
def pay(self, order, security_code):
print("Processing debit...")
order.status = "paid"
class CreditPaymentProcessor(PaymentProcessor):
def pay(self, order, security_code):
print("Processing credit...")
order.status = "paid"
# 3. KELAS BARU tanpa mengubah yg lama (SESUAI OCP)
class GopayPaymentProcessor(PaymentProcessor):
def pay(self, order, security_code): # gopay mungkin tdk butuh security_code, tapi ini masalah utk nanti (di 'L')
print("Processing gopay...")
order.status = "paid"
Sekarang, untuk menambah metode baru, kita cukup buat file baru berisi kelas baru. Kita tidak menyentuh kode lama.
👉 Apa yang bisa kamu lakukan sekarang:
- Cari blok if/elif/else besar di kodemu yang memeriksa "tipe" sesuatu.
- Blok if/elif/else adalah tanda paling jelas pelanggaran OCP, karena untuk menambah tipe baru, kamu harus mengedit blok itu.
- Gantilah dengan Abstraksi (membuat base class) dan implementasi subclass yang berbeda-beda.
L - Liskov Substitution Principle (LSP)
Prinsip Substitusi Liskov
- Konsep Inti: Objek dari subclass (anak) harus bisa menggantikan objek dari base class (induk) tanpa merusak program.
- Studi Kasus (Masalah): Kita mau menambah PaypalPaymentProcessor. Masalahnya, PayPal tidak pakai security_code, tapi pakai email.
- Jika kita paksa pay(self, order, email) di anak, signature method-nya beda dengan induk (pay(self, order, security_code)). Ini melanggar kontrak.
- Jika kita tetap pakai security_code tapi isinya sebenarnya email, itu menipu dan membingungkan.
- Sudut Pandang Saya: Ini soal "menepati janji." Jika induk berjanji method pay butuh security_code, semua anak harus patuh. Jika ada anak yang tidak bisa patuh, berarti desain abstraksi kita salah.
- Solusi: Pindahkan parameter yang berbeda-beda itu dari method ke constructor (__init__).
- Buat kontrak pay menjadi lebih umum: pay(self, order).
- Biarkan data spesifik (seperti security_code atau email) diminta saat objek dibuat.
Python
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, order): # Dibuat lebih umum
pass
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code): # Minta di constructor
self.security_code = security_code
def pay(self, order):
print(f"Processing debit with code: {self.security_code}")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor):
def __init__(self, email): # Minta di constructor
self.email = email
def pay(self, order):
print(f"Processing paypal with email: {self.email}")
order.status = "paid"
Sekarang, semua subclass menepati janji pay(self, order) dan tetap bisa membawa data unik mereka sendiri.
👉 Apa yang bisa kamu lakukan sekarang:
- Periksa subclass-mu. Apakah ada method yang kamu override tapi parameternya kamu ubah?
- Apakah ada method yang kamu override tapi di dalamnya kamu raise Exception("Not implemented")? (Ini juga dibahas di 'I').
- Jika ya, pertimbangkan untuk memindahkan parameter spesifik itu ke __init__.
I - Interface Segregation Principle (ISP)
Prinsip Pemisahan Interface (Kontrak)
- Konsep Inti: Jangan paksa client (kelas pengguna) untuk bergantung pada method yang tidak mereka gunakan.
- Studi Kasus (Masalah): Bagaimana jika beberapa metode (Debit, Gopay) butuh Otorisasi SMS (auth_sms), tapi yang lain (Credit, Paypal) tidak?
- Jika kita tambahkan abstractmethod auth_sms() di PaymentProcessor (induk), maka CreditPaymentProcessor dan PaypalPaymentProcessor dipaksa harus punya method auth_sms!
- Ini memaksa mereka membuat method "kosong" atau melempar exception, yang sangat kotor.
- Sudut Pandang Saya: Ini seperti kamu pesan Nasi Goreng (kelas Credit), tapi dipaksa harus tambah topping Telur Asin (method auth_sms) yang tidak kamu suka, hanya karena Telur Asin ada di "Menu Utama" (induk PaymentProcessor).
- Solusi: Pisahkan kontraknya! Buat kontrak yang lebih kecil dan spesifik.
- Kita bisa pakai pewarisan (Inheritance): Buat PaymentProcessor (dasar) dan PaymentProcessorWithAuth (yang mewarisi PaymentProcessor + menambah auth_sms).
- Solusi yang lebih baik (mengarah ke 'D'): Gunakan Komposisi.
Python
# Solusi Komposisi (lebih fleksibel)
# Kelas terpisah KHUSUS untuk otorisasi
class SMSAuthorizer:
def __init__(self):
self.authorized = False
def verify_code(self, code):
print(f"Verifying SMS code {code}")
self.authorized = True
def is_authorized(self):
return self.authorized
# Kelas payment HANYA minta 'authorizer' jika butuh
class DebitPaymentProcessor(PaymentProcessor):
def __init__(self, security_code, authorizer: SMSAuthorizer): # Minta via Komposisi
self.security_code = security_code
self.authorizer = authorizer
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing debit...")
order.status = "paid"
class PaypalPaymentProcessor(PaymentProcessor):
def __init__(self, email): # Tidak butuh authorizer
self.email = email
def pay(self, order):
# Tidak perlu cek otorisasi
print("Processing paypal...")
order.status = "paid"
👉 Apa yang bisa kamu lakukan sekarang:
- Lihat base class (induk) kamu. Apakah dia punya banyak sekali abstract method?
- Tanyakan: "Apakah semua anakku pasti butuh semua method ini?"
- Jika jawabannya "tidak", pecah base class itu menjadi beberapa base class yang lebih kecil dan spesifik.
D - Dependency Inversion Principle (DIP)
Prinsip Pembalikan Ketergantungan
- Konsep Inti: Kelas high-level (seperti DebitPaymentProcessor) tidak boleh bergantung pada kelas low-level (seperti SMSAuthorizer). Keduanya harus bergantung pada Abstraksi.
- Studi Kasus (Masalah): Solusi kita di 'I' sudah bagus, tapi masih ada masalah: DebitPaymentProcessor kita terkunci mati pada SMSAuthorizer. Lihat type hint-nya: authorizer: SMSAuthorizer.
- Bagaimana jika kita mau ganti otorisasi via WhatsAppAuthorizer? Kita harus mengedit kelas DebitPaymentProcessor! Ini melanggar OCP.
- Sudut Pandang Saya: Ini adalah prinsip plug-and-play. Bayangkan DebitPaymentProcessor adalah laptop. SMSAuthorizer adalah charger bawaan. Jika charger-nya rusak, laptopnya tidak bisa dipakai. DIP ingin laptop kita punya colokan standar (seperti USB-C, alias Abstraksi), sehingga kita bisa pakai charger merek apapun (SMS, WhatsApp, dll).
- Solusi: "Balikkan" ketergantungannya.
- Buat "colokan standar" (Abstraksi): Authorizer.
- Buat SMSAuthorizer dan WhatsAppAuthorizer "mencolok" ke standar itu (mewarisi dari Authorizer).
- Buat DebitPaymentProcessor bergantung pada "colokan standar" (Authorizer), bukan pada "merek charger" (SMSAuthorizer).
Python
# 1. Buat "Colokan Standar" (Abstraksi)
class Authorizer(ABC):
@abstractmethod
def is_authorized(self) -> bool:
pass
# 2. Buat "Charger Merek A" (Konkret)
class SMSAuthorizer(Authorizer):
def verify_code(self, code): ...
def is_authorized(self) -> bool: ...
# 3. Buat "Charger Merek B" (Konkret)
class WhatsAppAuthorizer(Authorizer):
def verify_wa_code(self, code): ...
def is_authorized(self) -> bool: ...
# 4. Laptop kini bergantung pada "Colokan Standar"
class DebitPaymentProcessor(PaymentProcessor):
# BERGANTUNG PADA ABSTRAKSI, BUKAN KONKRET!
def __init__(self, security_code, authorizer: Authorizer):
self.security_code = security_code
self.authorizer = authorizer
def pay(self, order):
if not self.authorizer.is_authorized():
raise Exception("Not authorized")
print("Processing debit...")
order.status = "paid"
Sekarang, DebitPaymentProcessor tidak peduli lagi dia dikasih SMSAuthorizer atau WhatsAppAuthorizer, selama benda itu adalah sebuah Authorizer. Ini powerful banget.
👉 Apa yang bisa kamu lakukan sekarang:
- Lihat parameter __init__ di kelas-kelasmu.
- Apakah ada parameter yang merupakan objek dari kelas konkret lain? (Contoh: authorizer: SMSAuthorizer).
- Jika ya, itu adalah tight coupling (ketergantungan kaku).
- Gantilah dependensi itu ke abstraksi (Contoh: authorizer: Authorizer). Ini akan membuat kodemu super fleksibel dan mudah di-tes (karena gampang di-mock).
🏁 Checklist & Tindak Lanjut Kamu
Saya tahu ini materi yang padat, dan jujur, saya sendiri butuh waktu untuk "nyantol" di kepala. Tapi setelah diterapkan, efeknya luar biasa pada kualitas kode yang saya hasilkan di tim.
Berikut adalah checklist yang bisa kamu pakai untuk mulai mempraktikkan ini sekarang juga:
- Mulai dari yang Terlihat (Clean Code):
- [ ] Install black di proyekmu. Rapikan semua file.
- [ ] Cari satu loop for i in range(len(...)) dan refactor menggunakan zip() atau list comprehension.
- Cari Pelanggaran Pertama (SRP):
- [ ] Cari 1 (satu) kelas paling "gemuk" di proyekmu.
- [ ] Identifikasi tanggung jawabnya yang kedua (misal: "mengurus data X DAN memvalidasi Y").
- [ ] Pisahkan tanggung jawab kedua itu ke kelas baru (misal: YValidator).
- Cari Blok "If" Jahat (OCP):
- [ ] Cari 1 (satu) blok if/elif/else yang memeriksa "tipe" (misal: if type == "admin", elif type == "user").
- [ ] Buat abstract base class (Induk) untuk "tipe" tersebut.
- [ ] Ubah setiap if menjadi subclass (Anak) yang mengimplementasikan method dari Induk.
- Cari Ketergantungan Kaku (DIP):
- [ ] Cari 1 (satu) kelas yang di dalam __init__-nya menerima objek dari kelas konkret lain (misal: def __init__(self, db: MySQLDatabase))
- [ ] Buat abstraksi untuk kelas konkret itu (misal: abstract class Database).
- [ ] Ubah dependensi di __init__ agar bergantung pada abstraksi (misal: def __init__(self, db: Database)).
Jangan coba lakukan semuanya sekaligus. Pilih satu poin dari checklist di atas dan terapkan di kodemu. Kamu akan merasakan sendiri bedanya.
Terima kasih sudah menyimak. Seperti biasa, banyak belajar biar bisa bantu banyak orang! Selamat ngoding!
🧠 Glosarium SOLID & Clean Code: Dari Awam Jadi Paham
Berikut adalah istilah-istilah yang sering membuat bingung, saya coba jelaskan dengan analogi sederhana.
1. Refactor (Refaktorisasi)
- Penjelasan Awam: Merapikan "kamar" tanpa memindahkannya ke rumah lain.
- Penjelasan Teknis: Proses mengubah struktur internal kode untuk membuatnya lebih bersih, lebih mudah dibaca, dan lebih efisien, tanpa mengubah perilakunya dari luar.
- Analogi Dapur: Bayangkan dapur Anda. Anda bisa memasak (kode running), tapi bumbu berantakan, panci di mana-mana. Refactor adalah saat Anda meluangkan waktu untuk memasang rak bumbu, mengelompokkan panci, dan memberi label pada toples. Hasil masakannya (output) tetap sama, tapi proses memasak berikutnya jadi 10x lebih cepat dan menyenangkan.
- Contoh Kasus Kita: Saat kita mengubah total_price() dari for i in range(len(self.prices)) menjadi for qty, price in zip(...). Output-nya sama persis (misal: 210), tapi kodenya jadi jauh lebih "Pythonic" dan mudah dibaca.
2. Abstraction (Abstraksi)
- Penjelasan Awam: Menyembunyikan kerumitan.
- Penjelasan Teknis: Menyembunyikan detail implementasi yang rumit dan hanya menunjukkan fungsionalitas yang esensial (penting) kepada pengguna.
- Analogi Mobil: Anda tidak perlu tahu cara kerja mesin, busi, dan transmisi untuk mengemudikan mobil. Anda hanya perlu tahu cara kerja setir, pedal gas, dan rem. Setir, gas, dan rem itulah abstraksi dari sebuah mobil.
- Contoh Kasus Kita: Kelas PaymentProcessor(ABC) adalah abstraksi. Dia adalah "kontrak" yang bilang, "Saya tidak peduli kamu Debit, Kredit, atau Gopay. Yang penting, kamu HARUS punya tombol pay()."
3. Concrete Class (Kelas Konkret)
- Penjelasan Awam: Benda nyata-nya.
- Penjelasan Teknis: Kelas yang menyediakan implementasi (kode) nyata untuk semua metode yang dijanjikan oleh abstraksi/induknya. Ini adalah kelas yang bisa Anda buat objeknya.
- Analogi Mobil: Jika "Mobil" (Abstraksi) adalah ide umumnya, maka Toyota Avanza atau Honda Brio (Kelas Konkret) adalah mobil nyata yang bisa Anda beli dan kendarai.
- Contoh Kasus Kita: DebitPaymentProcessor dan PaypalPaymentProcessor adalah kelas konkret. Mereka adalah implementasi nyata dari "janji" yang ada di PaymentProcessor(ABC).
4. Inheritance (Pewarisan)
- Penjelasan Awam: Hubungan "adalah sejenis".
- Penjelasan Teknis: Mekanisme di mana sebuah kelas (anak/subclass) mewarisi properti dan metode dari kelas lain (induk/base class).
- Analogi Keluarga: Anda (anak) mewarisi beberapa sifat dari orang tua Anda (induk), seperti warna mata atau nama keluarga. Tapi Anda juga punya sifat unik Anda sendiri.
- Contoh Kasus Kita: DebitPaymentProcessor adalah sejenis PaymentProcessor. Dia mewarisi "kewajiban" untuk punya method pay().
5. Composition (Komposisi)
- Penjelasan Awam: Hubungan "memiliki sebuah".
- Penjelasan Teknis: Mekanisme di mana sebuah kelas berisi (memiliki) instance atau objek dari kelas lain untuk menjalankan tugasnya.
- Analogi Pekerja & Alat: Seorang tukang kayu (kelas Tukang) memiliki sebuah palu (objek Palu). Tukang kayu bukan sejenis palu, tapi dia menggunakan palu untuk bekerja.
- Contoh Kasus Kita: DebitPaymentProcessor memiliki sebuah Authorizer. Dia bukan sejenis Authorizer, tapi dia menggunakan Authorizer untuk memverifikasi keamanan sebelum bayar.
6. Coupling (Ketergantungan)
- Penjelasan Awam: Seberapa "lengket" dua bagian saling terhubung.
- Penjelasan Teknis: Ukuran seberapa besar dua modul/kelas saling bergantung satu sama lain. Ada dua jenis:
- Tight Coupling (Lengket): Lampu yang kabelnya disolder mati ke tembok. Kalau lampunya putus, Anda harus bobok tembok untuk ganti. Repot!
- Loose Coupling (Longgar): Lampu yang pakai steker dan stopkontak. Kalau lampunya putus, tinggal cabut dan ganti baru. Mudah!
- Contoh Kasus Kita:
- Tight Coupling: (Masalah di Prinsip D) Saat DebitPaymentProcessor "disolder mati" ke SMSAuthorizer. Dia tidak mau tahu authorizer lain.
- Loose Coupling: (Solusi di Prinsip D) Saat DebitPaymentProcessor diganti agar bergantung pada Authorizer (abstraksi/stopkontaknya). Sekarang dia loose coupled, bisa dicolok SMSAuthorizer atau WhatsAppAuthorizer.
7. Dependency Inversion (Pembalikan Ketergantungan)
- Penjelasan Awam: "Jangan panggil kami, kami yang akan panggil Anda." (Versi Hollywood).
- Penjelasan Teknis: Ini adalah cara mencapai Loose Coupling. Prinsip ini membalikkan arah ketergantungan. Alih-alih modul level tinggi (misal: PaymentProcessor) bergantung pada modul level rendah (misal: SMSAuthorizer), keduanya kini bergantung pada Abstraksi (misal: Authorizer).
- Analogi Adaptor Universal: Bayangkan Anda punya laptop (modul level tinggi) dengan colokan aneh (dependensi ke SMSAuthorizer). Anda tidak bisa pakai colokan lain. Dependency Inversion adalah saat Anda memutuskan: "Saya tidak mau pusing! Laptop saya harus pakai colokan standar USB-C (Abstraksi)." Sekarang, charger merek apapun (modul level rendah) yang ingin dipakai HARUS menyesuaikan diri menjadi USB-C. Ketergantungannya dibalik.
🚀 Cara Mengajarkan Ilmu Ini (Agar Anda Semakin Paham)
Ini adalah template yang bisa Anda gunakan untuk menjelaskan materi ini ke rekan kerja, junior, atau komunitas Anda. Kuncinya adalah bercerita dan membangun masalah langkah demi langkah.
Judul Sesi: "Menulis Kode Anti-Rapuh: 5 Jurus SOLID dengan Python"
Audiens: Programmer pemula atau menengah yang sudah paham dasar OOP (Class, Object).
Struktur Pengajaran Anda:
Bagian 1: "Kenapa Repot-Repot?" (Motivasi)
"Halo teman-teman. Hari ini kita akan bicara tentang sesuatu yang membedakan programmer junior dan senior: yaitu cara menulis kode yang tidak hanya jalan, tapi juga mudah dirawat.
Pernah merasa takut mengubah satu baris kode, karena khawatir 5 fitur lain jadi rusak? Atau pernah dapat warisan kode dari orang lain yang saking pusingnya, Anda lebih memilih bikin ulang dari nol?
Nah, masalah itu terjadi bukan karena programmernya bodoh. Tapi karena kodenya rapuh (fragile) dan kaku (rigid). Hari ini, kita akan belajar 5 jurus dari Robert C. Martin yang disebut SOLID, agar kode kita jadi fleksibel dan tangguh."
Bagian 2: Studi Kasus: "Warung Online" (Mulai dari Kode Jelek)
"Mari kita mulai dengan kode yang 'normal'. Ini adalah kelas Order untuk warung online kita. (Tunjukkan kode awal yang kelas Order-nya melakukan semuanya)."
Python
class Order:
# ...
def add_item(...): ...
def total_price(...): ...
def pay(self, payment_type, ...):
if payment_type == "debit": ...
elif payment_type == "credit": ...
# ...
"Kode ini jalan. Tidak ada yang salah. Tapi... mari kita uji."
Bagian 3: Membedah SOLID (Masalah -> Solusi)
Ini adalah bagian terpenting. Jangan jelaskan 5 prinsip sekaligus. Jelaskan satu per satu sebagai solusi atas masalah yang baru muncul.
- S - Single Responsibility:
- Timbulkan Masalah: "Teman-teman, apa yang terjadi kalau kita mau menambah metode pembayaran baru, misal gopay? Kita harus mengedit kelas Order. Apa yang terjadi kalau kita mau mengubah cara hitung total_price? Kita juga mengedit kelas Order.
- Tawarkan Solusi (SRP): "Kelas ini terlalu 'gemuk'. Dia punya dua tanggung jawab: mengurus order DAN mengurus pembayaran. Menurut prinsip pertama, Single Responsibility, sebuah kelas hanya boleh punya satu alasan untuk berubah. Ayo kita pisah!"
- Aksi: Tunjukkan kode refactor di mana Anda memisah PaymentProcessor dari Order.
- O - Open/Closed:
- Timbulkan Masalah: "Oke, sekarang kita punya PaymentProcessor. Tapi kalau mau nambah gopay, kita tetap harus mengedit kelas ini kan? Nambah elif payment_type == 'gopay'. Ini melanggar prinsip kedua: Open/Closed. Kode harus terbuka untuk ditambah (ekstensi) tapi tertutup untuk diubah (modifikasi)."
- Tawarkan Solusi (OCP): "Caranya adalah pakai Abstraksi. Kita buat 'kontrak' atau 'cetakan'-nya dulu."
- Aksi: Tunjukkan kode di mana Anda membuat PaymentProcessor(ABC) dan kelas-kelas turunan (DebitPaymentProcessor, CreditPaymentProcessor). "Lihat, untuk nambah GopayPaymentProcessor, kita tinggal bikin file baru. Tidak perlu 'menyentuh' kode yang lama."
- L - Liskov Substitution:
- Timbulkan Masalah: "Sekarang kita mau nambah PaypalPaymentProcessor. Tapi... PayPal kan tidak pakai security_code, dia pakainya email. Waduh! Kontrak kita (pay(self, order, security_code)) jadi tidak cocok. Gimana dong?"
- Tawarkan Solusi (LSP): "Ini melanggar prinsip Liskov Substitution. Anak (Paypal) tidak bisa menggantikan Induk (PaymentProcessor) dengan sempurna. Solusinya? Jangan minta parameter unik di method pay(). Pindahkan ke __init__!"
- Aksi: Tunjukkan kode refactor di mana pay() jadi pay(self, order) dan data unik (email/security_code) diminta di __init__.
- I - Interface Segregation:
- Timbulkan Masalah: "Klien minta fitur baru. Khusus untuk Debit dan Gopay, harus ada Otorisasi SMS (auth_sms). Tapi Credit dan PayPal tidak perlu. Kalau kita tambahkan auth_sms di 'kontrak' Induk (PaymentProcessor), semua anak jadi 'dipaksa' punya auth_sms. Ini melanggar Interface Segregation. Jangan paksa klien (anak) pakai fitur yang tidak mereka butuh!"
- Tawarkan Solusi (ISP): "Kita jangan pakai pewarisan (Inheritance) terus. Kita pakai Komposisi (Composition). Kita buat kelas baru, SMSAuthorizer."
- Aksi: Tunjukkan kode di mana DebitPaymentProcessor kini memiliki SMSAuthorizer di dalam __init__-nya, sementara PaypalPaymentProcessor tidak.
- D - Dependency Inversion:
- Timbulkan Masalah: "Selesai? Belum. Lihat kelas DebitPaymentProcessor kita. Dia 'lengket' banget sama SMSAuthorizer. Bagaimana kalau besok klien minta ganti otorisasi pakai WhatsAppAuthorizer? Kita harus mengedit DebitPaymentProcessor lagi! Ini namanya Tight Coupling."
- Tawarkan Solusi (DIP): "Ini jurus terakhir: Dependency Inversion. Jangan bergantung pada 'merek' (SMSAuthorizer). Bergantunglah pada 'stopkontak' standarnya (Abstraksi Authorizer)."
- Aksi: Tunjukkan kode final di mana Anda membuat Authorizer(ABC) dan DebitPaymentProcessor sekarang bergantung pada Authorizer. "Sekarang, DebitPaymentProcessor kita jadi fleksibel. Mau dicolok SMSAuthorizer atau WhatsAppAuthorizer, bebas! Kodenya tidak perlu diubah lagi."
Bagian 4: Penutup dan "Apa Untungnya?"
"Jadi, teman-teman, setelah melalui 5 langkah ini, apa yang kita dapat?
- Kode Fleksibel: Mau nambah fitur pembayaran? Gampang.
- Kode Tangguh: Mengubah cara otorisasi WA tidak akan merusak logika PayPal.
- Kode Mudah Dites: Kita bisa ngetes DebitPaymentProcessor dengan 'Authorizer' palsu (mock) dengan mudah.
Ini adalah fondasi untuk membangun software yang bisa bertahan lama."
👉 Apa yang bisa Anda lakukan sekarang (Faisal):
- Pilih 1 Prinsip: Jangan ajarkan kelimanya sekaligus jika audiens Anda baru. Pilih satu saja, misal SRP.
- Buat Analogi Sendiri: Coba buat analogi dari dunia Anda (misal: peternakan ayam KUB). "Kelas Ayam yang 'gemuk' adalah yang mengurus data pakan, data kandang, DAN data penjualan telur. Ini melanggar SRP!"
- Praktikkan Langsung: Buka salah satu script Anda. Cari satu pelanggaran SRP atau OCP dan coba perbaiki. Mengalami sendiri adalah guru terbaik.
Selamat mencoba mengajar! Ini akan sangat memperkuat pemahaman Anda.