40. Menulis Kode yang Bersih dan Mudah Dibaca
Sepanjang buku ini, kamu sudah mempelajari sintaks Python, struktur data, alur kontrol, fungsi, kelas, dan banyak konsep pemrograman lainnya. Sekarang kamu bisa menulis program yang berfungsi. Tapi ada perbedaan penting antara kode yang berjalan dan kode yang dapat dipelihara—kode yang bisa kamu dan orang lain pahami, ubah, dan debug berbulan-bulan atau bertahun-tahun kemudian.
Bab ini berfokus pada menulis kode yang bersih dan mudah dibaca. Kamu akan mempelajari konvensi dan praktik yang membuat kode Python terlihat profesional dan mudah dipelihara. Ini bukan sekadar aturan yang arbitrer—ini adalah pedoman yang sudah teruji di lapangan yang membuat kolaborasi lebih mudah, mengurangi bug, dan membantu kamu memahami kode kamu sendiri saat kamu kembali membukanya nanti.
40.1) Kenapa Gaya Itu Penting: Membaca vs. Menulis Kode
40.1.1) Kode Lebih Sering Dibaca Daripada Ditulis
Saat kamu menulis kode, kamu menghabiskan menit atau jam untuk membuatnya. Tetapi kode itu akan dibaca berkali-kali: saat kamu men-debug-nya, saat kamu menambahkan fitur, saat developer lain bekerja dengannya, dan saat kamu kembali berbulan-bulan kemudian sambil mencoba mengingat apa yang dilakukan kode tersebut.
Pertimbangkan kode ini yang berfungsi tetapi bergaya buruk:
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
def c(l):
t=0
for i in l:
t=t+i
return t/len(l)
data=[85,92,78,90,88]
result=c(data)
print(result) # Output: 86.6Kode ini bekerja dengan sempurna. Ia menghitung rata-rata dari sebuah list angka. Tetapi memahami apa yang dilakukannya membutuhkan analisis yang cermat. Sekarang bandingkan dengan versi ini:
def calculate_average(numbers):
"""Hitung mean aritmetika dari sebuah list angka."""
total = 0
for number in numbers:
total = total + number
return total / len(numbers)
test_scores = [85, 92, 78, 90, 88]
average_score = calculate_average(test_scores)
print(average_score) # Output: 86.6Apa yang membuat versi kedua lebih baik?
- Nama fungsi (
calculate_average) menyatakan tujuan dengan jelas - Nama variabel (
numbers,total,test_scores) deskriptif - Docstring menjelaskan apa yang dilakukan fungsi
- Spasi yang tepat membuat strukturnya jelas
- Siapa pun bisa memahami kode ini tanpa harus mempelajarinya lama
Kedua versi menghasilkan output yang identik, tetapi versi kedua langsung bisa dipahami.
Inti utamanya: Kamu menulis kode sekali, tetapi kamu membacanya puluhan atau ratusan kali. Meluangkan beberapa detik ekstra untuk penamaan dan formatting yang jelas menghemat berjam-jam kebingungan nanti.
40.1.2) Keterbacaan Mengurangi Bug
Kode yang jelas lebih mudah di-debug karena kamu bisa cepat memahami apa yang dilakukan setiap bagiannya. Saat nama variabel deskriptif dan strukturnya rapi, kamu bisa lebih mudah melihat kesalahan logika.
# Sulit di-debug - variabel-variabel ini merepresentasikan apa?
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
def process(x, y):
if x > y:
return x * (1 - y)
return x
result = process(100, 0.1)# Mudah di-debug - jelas apa yang terjadi
def apply_discount(price, discount_rate):
"""Hitung harga setelah menerapkan tingkat diskon (0.0 sampai 1.0)."""
discount_amount = price * discount_rate
final_price = price - discount_amount
return final_price
original_price = 100
discount = 0.1 # diskon 10%
final_price = apply_discount(original_price, discount)
print(f"Final price: ${final_price}")
# Output: Final price: $90.0Di versi kedua, kamu bisa langsung melihat logikanya: "Kita menghitung jumlah diskon, lalu menguranginya dari harga." Di versi pertama, kamu harus melacak secara mental apa arti x dan y serta menebak apa maksud x * (1 - y).
40.1.3) Konsistensi Memungkinkan Kolaborasi
Saat semua orang di sebuah tim mengikuti konvensi gaya yang sama, kode jadi bisa diprediksi. Kamu tidak menghabiskan energi mental untuk mengurai gaya formatting yang berbeda-beda—kamu bisa fokus memahami logikanya.
Python punya panduan gaya resmi bernama PEP 8 (Python Enhancement Proposal 8). PEP 8 mendefinisikan konvensi untuk:
- Cara menamai variabel, fungsi, dan kelas
- Cara memformat kode (spasi, panjang baris, indentasi)
- Kapan harus memakai komentar dan docstring
- Cara mengatur import
Mengikuti PEP 8 berarti kode kamu akan terlihat familiar bagi programmer Python lain, sehingga kolaborasi lebih mulus. Kita akan membahas pedoman PEP 8 yang penting di bagian berikutnya.
40.2) Konvensi Penamaan: Variabel, Fungsi, dan Kelas (PEP 8)
40.2.1) Prinsip Umum Penamaan
Nama yang baik itu deskriptif dan tidak ambigu. Nama harus memberi tahu apa yang direpresentasikan atau dilakukan sesuatu tanpa mengharuskan kamu membaca implementasinya.
Prinsip utama:
- Gunakan kata lengkap, bukan singkatan (kecuali yang sangat umum seperti
id,url,html) - Spesifik:
user_countlebih baik daripadacount,calculate_total_pricelebih baik daripadacalculate - Hindari nama satu huruf kecuali untuk loop yang sangat singkat atau rumus matematika
- Jangan masukkan informasi tipe ke dalam nama (Python bertipe dinamis)
# Nama buruk - tidak jelas apa yang direpresentasikan
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
# Apa itu 'n'? Angka? Nama? Node?
# Apa itu 'd'? Tanggal? Jarak? Durasi?
# Apa itu 'l'? Terlihat seperti angka 1!
n = "Alice"
d = 25
l = [1, 2, 3]
calc = lambda x: x * 2
# Nama baik - jelas dan deskriptif
student_name = "Alice"
age_in_years = 25
test_scores = [1, 2, 3]
double_value = lambda x: x * 2Pengecualian: variabel loop yang pendek
# Dapat diterima: sangat singkat, konteksnya jelas
for i in range(10):
print(i)
for x, y in coordinates:
distance = (x**2 + y**2) ** 0.5
# Tapi lebih baik gunakan nama deskriptif agar jelas
for student_index in range(len(students)):
print(students[student_index])
for point_x, point_y in coordinates:
distance = (point_x**2 + point_y**2) ** 0.540.2.2) Nama Variabel dan Fungsi: snake_case
Di Python, variabel dan fungsi menggunakan snake_case: semua huruf kecil dengan kata dipisahkan oleh underscore.
# Variabel
user_name = "Bob"
total_price = 99.99
is_valid = True
max_retry_count = 3
# Fungsi
def calculate_tax(amount, rate):
"""Hitung pajak untuk jumlah tertentu."""
return amount * rate
def send_email_notification(recipient, message):
"""Kirim email ke penerima yang ditentukan."""
print(f"Sending to {recipient}: {message}")
# Menggunakan fungsi
tax_amount = calculate_tax(100, 0.08)
send_email_notification("user@example.com", "Welcome!")Kenapa snake_case? Sangat mudah dibaca. Underscore membuat batas kata yang jelas, sehingga nama mudah dipindai. Bandingkan calculatetotalprice (sulit dibaca) dengan calculate_total_price (langsung jelas).
40.2.3) Nama Konstanta: UPPER_SNAKE_CASE
Konstanta—nilai yang seharusnya tidak berubah selama eksekusi program—menggunakan UPPER_SNAKE_CASE: semua huruf besar dengan underscore.
# Konstanta di level modul
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
PI = 3.14159
DATABASE_URL = "postgresql://localhost/mydb"
def validate_password_length(password):
"""Periksa apakah password memenuhi syarat panjang minimum."""
MIN_PASSWORD_LENGTH = 8 # Konstanta di dalam fungsi
return len(password) >= MIN_PASSWORD_LENGTH
# Menggunakan konstanta
if login_attempts > MAX_LOGIN_ATTEMPTS:
print("Account locked")Penting: Python tidak punya sintaks konstanta bawaan. Tidak seperti beberapa bahasa (seperti const di JavaScript atau final di Java), Python tidak punya cara untuk mendeklarasikan bahwa sebuah variabel tidak bisa diubah.
Sebagai gantinya, programmer Python menggunakan konvensi penamaan untuk memberi sinyal maksud:
UPPER_SNAKE_CASEberarti: "Saya bermaksud ini sebagai konstanta—jangan diubah"- Ini adalah alat komunikasi antar programmer, bukan fitur bahasa
# Python tidak punya sintaks konstanta - ini hanya variabel biasa
MAX_LOGIN_ATTEMPTS = 3
# Python tidak akan mencegah kamu mengubahnya
MAX_LOGIN_ATTEMPTS = 5 # ❌ Secara teknis bisa, tapi melanggar konvensi
# Konvensi penamaan adalah sinyal tentang INTENT:
# "Saya menamai ini dengan HURUF BESAR untuk menunjukkan saya tidak ingin ini diubah"Praktik terbaik: Jika sebuah nilai memang perlu berubah selama eksekusi program, jangan menamainya sebagai konstanta:
# Nilai ini akan berubah - gunakan huruf kecil
max_login_attempts = 3
max_login_attempts = 5 # ✅ OK - namanya menunjukkan bisa berubah
# Nilai ini seharusnya tidak pernah berubah - gunakan HURUF BESAR
MAX_LOGIN_ATTEMPTS = 3
# Jangan meng-assign ulang nanti di kodeKonvensi ini membantu programmer memahami maksudmu dan menghindari bug. Saat kamu melihat MAX_LOGIN_ATTEMPTS, kamu tahu untuk tidak mengubahnya.
40.2.4) Nama Kelas: PascalCase
Nama kelas menggunakan PascalCase (juga disebut CapWords): setiap kata diawali huruf besar, tanpa underscore.
# Definisi class
class Student:
"""Merepresentasikan seorang siswa dengan nama dan nilai."""
def __init__(self, name):
self.name = name
self.grades = []
class ShoppingCart:
"""Mengelola item di keranjang belanja."""
def __init__(self):
self.items = []
def add_item(self, item):
"""Tambahkan item ke keranjang."""
self.items.append(item)
class DatabaseConnection:
"""Menangani koneksi database dan query."""
def __init__(self, url):
self.url = url
# Membuat instance (catatan: instance memakai nama variabel snake_case)
student = Student("Alice")
shopping_cart = ShoppingCart()
db_connection = DatabaseConnection("localhost")Kenapa PascalCase untuk kelas? Ini membedakan kelas dari fungsi dan variabel secara visual. Saat kamu melihat Student(), kamu langsung tahu itu membuat instance dari sebuah kelas. Saat kamu melihat calculate_average(), kamu tahu itu memanggil fungsi.
40.2.5) Nama Private dan Internal: Leading Underscore
Nama yang diawali satu underscore (_name) menandakan penggunaan internal—dimaksudkan untuk dipakai di dalam modul atau kelas, bukan oleh kode eksternal.
Python tidak punya sintaks untuk menandai method atau attribute sebagai "private" (tidak seperti private di Java atau C++). Sebagai gantinya, Python memakai konvensi penamaan dengan underscore di depan (_name) untuk mengomunikasikan maksud.
Arti _name:
- "Ini hanya untuk penggunaan internal"
- "Saya membuat ini untuk dipakai di dalam class/modul ini, bukan untuk kode eksternal"
- "Ini bisa berubah kapan saja di versi mendatang—jangan bergantung padanya"
class BankAccount:
"""Merepresentasikan akun bank dengan pelacakan saldo."""
def __init__(self, account_number, initial_balance):
self.account_number = account_number
self._balance = initial_balance # Attribute internal
def deposit(self, amount):
"""Tambahkan uang ke akun."""
if self._validate_amount(amount): # Method internal
self._balance += amount
def _validate_amount(self, amount):
"""Helper internal untuk memvalidasi jumlah transaksi."""
return amount > 0
def get_balance(self):
"""Kembalikan saldo saat ini."""
return self._balance
# Menggunakan class
account = BankAccount("12345", 1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# Secara teknis bisa, tapi melanggar konvensi
print(account._balance)
# Output: 1500 (works, but you shouldn't do this!)
# Secara teknis bisa, tapi melanggar konvensi
result = account._validate_amount(100)
# Output: True (works, but you shouldn't do this!)Poin utama: Python tidak bisa mencegah kamu mengakses _balance atau memanggil _validate_amount(). Underscore adalah sinyal antar programmer, bukan fitur keamanan.
Kenapa Konvensi Ini Ada
Karena Python tidak bisa memaksa privasi, underscore adalah cara penulis kelas menyampaikan maksudnya:
Yang disinyalkan underscore:
- "Ini implementasi internal—bisa berubah di versi mendatang"
- "Gunakan method publik saja—itu dijamin tetap stabil"
- "Kalau kamu bergantung pada detail internal, kode kamu bisa rusak saat saya mengupdate library"
Konvensi ini menciptakan sebuah kontrak: penulis kelas bebas mengubah implementasi internal (apa pun yang diawali _), tetapi harus menjaga antarmuka publik tetap stabil. Ini memungkinkan library berevolusi tanpa merusak kode pengguna.
40.2.6) Nama Khusus: Double Underscores
Nama dengan double underscore di depan dan belakang (__name__) adalah special method atau magic method yang didefinisikan oleh Python. Jangan membuat nama kamu sendiri dengan pola ini—ini disediakan untuk penggunaan Python.
class Point:
"""Merepresentasikan sebuah titik 2D."""
def __init__(self, x, y): # Method khusus: inisialisasi
self.x = x
self.y = y
def __str__(self): # Method khusus: representasi string
return f"Point({self.x}, {self.y})"
def __add__(self, other): # Method khusus: operator penjumlahan
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1) # Output: Point(1, 2)
print(p1 + p2) # Output: Point(4, 6)Seperti yang kita pelajari di Bab 31, method khusus ini memungkinkan operator overloading dan integrasi dengan fungsi bawaan Python.
40.2.7) Tabel Ringkasan Penamaan
| Tipe | Konvensi | Contoh |
|---|---|---|
| Variabel | snake_case | user_name, total_count |
| Fungsi | snake_case | calculate_tax(), send_email() |
| Konstanta | UPPER_SNAKE_CASE | MAX_SIZE, DEFAULT_TIMEOUT |
| Kelas | PascalCase | Student, ShoppingCart |
| Internal/Private (internal/private) | _leading_underscore | _balance, _validate() |
| Khusus/Magic | double_underscore | __init__, __str__ |
40.3) Tata Letak Kode: Indentasi, Spasi, dan Baris Kosong
40.3.1) Indentasi: Empat Spasi
Python menggunakan indentasi untuk mendefinisikan blok kode. Selalu gunakan 4 spasi per level indentasi—jangan pernah tab, dan jangan pernah mencampur tab dan spasi.
def calculate_grade(score):
"""Tentukan nilai huruf dari skor numerik."""
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
else:
return "F"
# Indentasi bertingkat: 4 spasi per level
def process_students(students):
"""Proses sebuah list data siswa."""
for student in students:
if student["active"]:
grade = calculate_grade(student["score"])
print(f"{student['name']}: {grade}")
students = [
{"name": "Alice", "score": 92, "active": True},
{"name": "Bob", "score": 78, "active": True}
]
process_students(students)
# Output:
# Alice: A
# Bob: CKenapa 4 spasi? Ini standar komunitas Python. Sebagian besar kode Python yang akan kamu temui memakai 4 spasi, jadi mengikuti konvensi ini membuat kode kamu konsisten dengan ekosistem.
Mengonfigurasi editor kamu: Editor kode modern bisa diatur agar memasukkan 4 spasi saat kamu menekan Tab. Ini memberi kenyamanan tombol Tab sambil tetap menjaga standar 4 spasi.
40.3.2) Panjang Baris Maksimum: 79 Karakter
PEP 8 merekomendasikan membatasi baris menjadi 79 karakter (dengan hingga 99 karakter untuk docstring dan komentar). Ini mungkin terasa membatasi, tetapi punya manfaat praktis:
- Kode tetap mudah dibaca di layar kecil
- Kamu bisa melihat dua file berdampingan
- Ini mendorong memecah ekspresi kompleks menjadi bagian yang lebih sederhana
Catatan: Banyak proyek modern memakai batas yang sedikit lebih panjang (88, 100, atau 120 karakter). Kuncinya adalah konsistensi di dalam proyekmu. Pilih satu batas dan patuhi.
# Terlalu panjang - sulit dibaca
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
def calculate_monthly_payment(principal, annual_rate, years):
return principal * (annual_rate / 12) * (1 + annual_rate / 12) ** (years * 12) / ((1 + annual_rate / 12) ** (years * 12) - 1)
# Lebih baik - dipecah menjadi baris yang mudah dibaca
def calculate_monthly_payment(principal, annual_rate, years):
"""Hitung cicilan bulanan pinjaman memakai rumus amortisasi."""
monthly_rate = annual_rate / 12
num_payments = years * 12
numerator = principal * monthly_rate * (1 + monthly_rate) ** num_payments
denominator = (1 + monthly_rate) ** num_payments - 1
return numerator / denominator
payment = calculate_monthly_payment(200000, 0.045, 30)
print(f"Monthly payment: ${payment:.2f}") # Output: Monthly payment: $1013.37Memecah baris panjang: Saat kamu perlu memecah baris, gunakan kelanjutan baris implisit di dalam tanda kurung, bracket, atau brace:
# Pemanggilan fungsi yang panjang
result = some_function(
first_argument,
second_argument,
third_argument,
fourth_argument
)
# List yang panjang
colors = [
"red", "green", "blue",
"yellow", "orange", "purple",
"pink", "brown", "gray"
]
# String yang panjang
message = (
"This is a very long message that needs to be broken "
"across multiple lines for readability. Python automatically "
"concatenates adjacent string literals."
)
print(message)
# Output: This is a very long message that needs to be broken across multiple lines for readability. Python automatically concatenates adjacent string literals.40.3.3) Spasi di Sekitar Operator dan Setelah Koma
Gunakan spasi di sekitar operator dan setelah koma untuk meningkatkan keterbacaan:
# Spasi buruk - padat dan sulit dibaca
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
x=5
y=x*2+3
result=calculate_tax(100,0.08)
data=[1,2,3,4,5]
# Spasi baik - jelas dan mudah dibaca
x = 5
y = x * 2 + 3
result = calculate_tax(100, 0.08)
data = [1, 2, 3, 4, 5]
# Spasi dalam ekspresi
total = (price * quantity) + shipping_cost
is_valid = (age >= 18) and (has_license == True)
# Spasi dalam definisi fungsi
def calculate_discount(price, discount_rate, minimum_purchase=0):
"""Hitung harga diskon jika pembelian minimum terpenuhi."""
if price >= minimum_purchase:
return price * (1 - discount_rate)
return pricePengecualian: Jangan gunakan spasi di sekitar = pada keyword argument atau nilai default parameter:
# Spasi yang benar untuk keyword argument
result = calculate_discount(price=100, discount_rate=0.1, minimum_purchase=50)
# Spasi yang benar untuk parameter default
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"40.3.4) Baris Kosong untuk Pemisahan Logis
Gunakan baris kosong untuk memisahkan bagian kode yang berbeda secara logis:
Dua baris kosong di antara fungsi dan class level teratas:
def first_function():
"""Fungsi pertama."""
pass
def second_function():
"""Fungsi kedua."""
pass
class MyClass:
"""Sebuah definisi class."""
passSatu baris kosong di antara method di dalam sebuah class:
class Student:
"""Merepresentasikan seorang siswa dengan nilai."""
def __init__(self, name):
self.name = name
self.grades = []
def add_grade(self, grade):
"""Tambahkan nilai ke catatan siswa."""
self.grades.append(grade)
def get_average(self):
"""Hitung rata-rata nilai siswa."""
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)Baris kosong di dalam fungsi untuk memisahkan langkah-langkah logis:
def process_order(order_items, customer):
"""Proses pesanan pelanggan dan hitung total."""
# Hitung subtotal
subtotal = 0
for item in order_items:
subtotal += item["price"] * item["quantity"]
# Terapkan diskon pelanggan
discount = 0
if customer["is_premium"]:
discount = subtotal * 0.1
# Hitung pajak
tax = (subtotal - discount) * 0.08
# Hitung total akhir
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}Baris kosong ini bertindak sebagai "paragraf" visual, membuat struktur kode langsung terlihat.
40.3.5) Menghindari Spasi di Akhir Baris
Jangan meninggalkan spasi di akhir baris—itu tidak terlihat tetapi bisa menyebabkan masalah pada sistem version control dan beberapa editor.
# Buruk - spasi di akhir baris yang tidak terlihat (ditunjukkan sebagai · untuk ilustrasi)
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
def calculate(x):···
return x * 2···
# Baik - tanpa spasi di akhir baris
def calculate(x):
return x * 2Sebagian besar editor modern bisa dikonfigurasi untuk menghapus trailing whitespace secara otomatis saat kamu menyimpan file.
40.4) Dokumentasi: Menulis Komentar dan Docstring yang Membantu
40.4.1) Kapan Menulis Komentar
Komentar menjelaskan kenapa kode melakukan sesuatu, bukan apa yang dilakukannya. Nama variabel dan fungsi yang baik seharusnya membuat bagian "apa" terlihat jelas.
# Komentar buruk - menyatakan hal yang sudah jelas
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
x = x + 1 # Tambahkan 1 ke x
# Komentar baik - menjelaskan kenapa
x = x + 1 # Sesuaikan untuk indexing berbasis nol
# Komentar buruk - redundan dengan kode
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
# Periksa apakah age lebih besar atau sama dengan 18
if age >= 18:
print("Adult")
# Komentar baik - menjelaskan logika bisnis
# Usia legal untuk minum alkohol di AS
if age >= 21:
print("Can purchase alcohol")Kapan komentar bernilai:
- Menjelaskan algoritma yang kompleks:
def binary_search(sorted_list, target):
"""Cari target dalam sorted list menggunakan binary search."""
left = 0
right = len(sorted_list) - 1
while left <= right:
# Hitung titik tengah, menghindari integer overflow
# (right + left) // 2 bisa overflow dengan indeks yang sangat besar
mid = left + (right - left) // 2
if sorted_list[mid] == target:
return mid
elif sorted_list[mid] < target:
left = mid + 1 # Target ada di setengah kanan
else:
right = mid - 1 # Target ada di setengah kiri
return -1 # Target tidak ditemukan- Memperjelas aturan bisnis yang tidak obvious:
def calculate_shipping_cost(weight, distance):
"""Hitung biaya pengiriman berdasarkan berat dan jarak."""
base_cost = 5.00
# Promo gratis ongkir untuk barang berat (kebijakan perusahaan per 2024)
# Ini mendorong pesanan dalam jumlah besar dan mengurangi biaya ongkir per unit
if weight > 50:
return 0
# Tarif standar: $0.50 per pound ditambah $0.10 per mile
# Berdasarkan kontrak carrier yang dinegosiasikan di Q1 2024
return base_cost + (weight * 0.50) + (distance * 0.10)- Mendokumentasikan workaround atau solusi sementara:
def process_data(data):
"""Proses record data yang masuk."""
# TODO: Ini perbaikan sementara untuk record yang tidak sesuai format
# Hapus setelah validasi data diimplementasikan di upstream
if not isinstance(data, list):
data = [data]
for record in data:
# Proses setiap record
pass40.4.2) Menulis Docstring yang Efektif
Docstring adalah komentar khusus yang mendokumentasikan modul, class, dan fungsi. Docstring ditulis di dalam triple quotes dan muncul sebagai statement pertama dalam definisi.
def calculate_bmi(weight_kg, height_m):
"""
Hitung Body Mass Index (BMI).
BMI dihitung sebagai berat dalam kilogram dibagi kuadrat tinggi dalam meter.
Args:
weight_kg: Berat dalam kilogram (float atau int)
height_m: Tinggi dalam meter (float atau int)
Returns:
float: Nilai BMI yang dihitung
Example:
>>> calculate_bmi(70, 1.75)
22.857142857142858
"""
return weight_kg / (height_m ** 2)
# Mengakses docstring
print(calculate_bmi.__doc__)
# Output:
# Hitung Body Mass Index (BMI).
#
# BMI dihitung sebagai berat dalam kilogram dibagi kuadrat tinggi dalam meter.
# ...Docstring satu baris untuk fungsi sederhana:
def square(x):
"""Kembalikan kuadrat dari x."""
return x * x
def is_even(n):
"""Return True jika n genap, False jika tidak."""
return n % 2 == 0Docstring multi-baris untuk fungsi yang kompleks:
def find_prime_factors(n):
"""
Temukan semua faktor prima dari sebuah bilangan bulat positif.
Fungsi ini mengembalikan list bilangan prima yang jika dikalikan
bersama-sama akan sama dengan angka input. Faktor dikembalikan dalam urutan menaik.
Args:
n: Bilangan bulat positif yang lebih besar dari 1
Returns:
list: Faktor prima dalam urutan menaik
Raises:
ValueError: Jika n kurang dari 2
Example:
>>> find_prime_factors(12)
[2, 2, 3]
>>> find_prime_factors(17)
[17]
"""
if n < 2:
raise ValueError("n must be at least 2")
factors = []
divisor = 2
while n > 1:
while n % divisor == 0:
factors.append(divisor)
n = n // divisor
divisor += 1
return factorsDocstring class:
class BankAccount:
"""
Merepresentasikan akun bank dengan operasi setoran dan penarikan.
Class ini menjaga saldo akun dan menyediakan method untuk
menyetor dan menarik uang. Semua transaksi divalidasi untuk mencegah saldo negatif.
Attributes:
account_number: Identifier unik untuk akun
balance: Saldo akun saat ini dalam dolar
"""
def __init__(self, account_number, initial_balance=0):
"""
Inisialisasi akun bank baru.
Args:
account_number: Identifier akun yang unik (string)
initial_balance: Saldo awal (default: 0)
"""
self.account_number = account_number
self.balance = initial_balance
def deposit(self, amount):
"""
Tambahkan uang ke akun.
Args:
amount: Jumlah yang akan disetor (harus positif)
Raises:
ValueError: Jika amount tidak positif
"""
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount40.4.3) Konvensi Docstring
Baris pertama: Ringkasan singkat tentang apa yang dilakukan fungsi/class. Harus muat dalam satu baris.
Baris kosong: Memisahkan ringkasan dari deskripsi detail.
Deskripsi detail: Jelaskan apa yang dilakukan fungsi, detail penting apa pun, dan cara menggunakannya.
Args/Parameters: Daftarkan setiap parameter beserta tipenya dan tujuannya.
Returns: Jelaskan apa yang dikembalikan fungsi dan tipenya.
Raises: Dokumentasikan exception apa pun yang mungkin dilempar fungsi.
Example: Tunjukkan penggunaan yang umum (opsional tetapi membantu).
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
"""
Hitung bunga majemuk pada investasi.
Menggunakan rumus bunga majemuk: A = P(1 + r/n)^(nt)
di mana A adalah jumlah akhir, P adalah pokok, r adalah suku bunga tahunan,
n adalah jumlah penggandaan per tahun, dan t adalah waktu dalam tahun.
Args:
principal: Jumlah investasi awal (float)
rate: Suku bunga tahunan dalam desimal (misalnya 0.05 untuk 5%)
time: Periode investasi dalam tahun (float)
compounds_per_year: Berapa kali bunga digandakan per tahun
(default: 1 untuk penggandaan tahunan)
Returns:
float: Jumlah akhir setelah bunga majemuk
Example:
>>> calculate_compound_interest(1000, 0.05, 10, 12)
1647.0095
"""
return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)40.4.4) Komentar TODO untuk Pekerjaan Mendatang
Gunakan komentar TODO untuk menandai area yang perlu diperhatikan di masa depan:
def process_payment(amount, payment_method):
"""Proses transaksi pembayaran."""
# TODO: Tambahkan dukungan untuk pembayaran cryptocurrency
# TODO: Implementasikan pemeriksaan deteksi fraud
if payment_method == "credit_card":
return process_credit_card(amount)
elif payment_method == "paypal":
return process_paypal(amount)
else:
raise ValueError(f"Unsupported payment method: {payment_method}")Banyak editor bisa mencari komentar TODO, sehingga mudah menemukan area yang perlu dikerjakan.
40.5) Mengorganisasi Kode Kamu: Import, Konstanta, Fungsi, dan Main
40.5.1) Struktur Modul Standar
Modul Python yang terorganisasi dengan baik mengikuti struktur ini:
- Docstring modul: Menjelaskan apa yang dilakukan modul
- Import: Standard library, third-party, lalu import lokal
- Konstanta: Konstanta level modul
- Fungsi dan class: Kode utama
- Blok eksekusi main: Kode yang berjalan ketika script dieksekusi
"""
student_manager.py
Kelola data siswa termasuk nilai dan perhitungan GPA.
Modul ini menyediakan fungsi untuk menambahkan siswa, mencatat nilai,
dan menghitung grade point average.
"""
# Import standard library
import sys
from datetime import datetime
# Import third-party (jika ada)
# import requests
# Import lokal (jika ada)
# from .database import save_student
# Konstanta
MAX_GRADE = 100
MIN_GRADE = 0
PASSING_GRADE = 60
# Fungsi
def calculate_gpa(grades):
"""
Hitung GPA dari sebuah list nilai numerik.
Args:
grades: List nilai numerik (0-100)
Returns:
float: GPA pada skala 4.0
"""
if not grades:
return 0.0
average = sum(grades) / len(grades)
# Konversi ke skala 4.0
if average >= 90:
return 4.0
elif average >= 80:
return 3.0
elif average >= 70:
return 2.0
elif average >= 60:
return 1.0
else:
return 0.0
def validate_grade(grade):
"""
Periksa apakah sebuah nilai berada dalam rentang yang valid.
Args:
grade: Nilai numerik yang akan divalidasi
Returns:
bool: True jika grade valid, False jika tidak
"""
return MIN_GRADE <= grade <= MAX_GRADE
# Eksekusi main
if __name__ == "__main__":
# Kode yang berjalan saat script dieksekusi langsung
test_grades = [85, 92, 78, 88]
gpa = calculate_gpa(test_grades)
print(f"GPA: {gpa}") # Output: GPA: 3.040.5.2) Organisasi Import
Kelompokkan import menjadi tiga bagian, dipisahkan oleh baris kosong:
- Import standard library: Modul bawaan Python
- Import third-party: Package yang terinstal (seperti
requests,numpy) - Import lokal: Modul milikmu sendiri
# Import standard library
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
# Import third-party
import requests
from flask import Flask, render_template
# Import aplikasi lokal
from myapp.database import connect_db
from myapp.models import User, Product
from myapp.utils import format_currencyGaya import:
# Import seluruh modul
import math
result = math.sqrt(16) # Output: 4.0
# Import item tertentu
from math import sqrt, pi
result = sqrt(16) # Output: 4.0
# Import dengan alias
import numpy as np
array = np.array([1, 2, 3])
# Import beberapa item
from os import path, getcwd, listdirHindari wildcard import (from module import *)—ini membuatnya tidak jelas nama berasal dari mana:
# Buruk - tidak jelas sqrt berasal dari mana
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
from math import *
result = sqrt(16)
# Baik - import eksplisit
from math import sqrt
result = sqrt(16)40.5.3) Mengorganisasi Konstanta
Letakkan konstanta level modul dekat bagian atas, setelah import:
"""Pengaturan konfigurasi untuk aplikasi."""
import os
# Konstanta aplikasi
APP_NAME = "Student Manager"
VERSION = "1.0.0"
DEBUG_MODE = True
# Konfigurasi database
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///students.db")
MAX_CONNECTIONS = 10
# Aturan bisnis
MAX_STUDENTS_PER_CLASS = 30
PASSING_GRADE = 60
GRADE_WEIGHTS = {
"homework": 0.3,
"midterm": 0.3,
"final": 0.4
}
def calculate_final_grade(homework, midterm, final):
"""Hitung nilai akhir berbobot."""
return (
homework * GRADE_WEIGHTS["homework"] +
midterm * GRADE_WEIGHTS["midterm"] +
final * GRADE_WEIGHTS["final"]
)40.5.4) Urutan Fungsi yang Logis
Atur fungsi dalam urutan yang logis:
- Fungsi publik dulu: Fungsi yang dimaksudkan untuk dipakai modul lain
- Fungsi helper setelahnya: Fungsi internal yang mendukung fungsi publik
- Fungsi yang terkait dikelompokkan: Kelompokkan fungsi yang bekerja bersama
"""Modul pemrosesan pesanan."""
# Fungsi API publik
def process_order(order_items, customer):
"""
Proses pesanan pelanggan.
Ini adalah entry point utama untuk pemrosesan pesanan.
"""
subtotal = _calculate_subtotal(order_items)
discount = _calculate_discount(subtotal, customer)
tax = _calculate_tax(subtotal - discount)
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}
def validate_order(order_items):
"""Validasi bahwa sebuah pesanan berisi item yang valid."""
if not order_items:
return False
for item in order_items:
if not _validate_item(item):
return False
return True
# Fungsi helper internal
def _calculate_subtotal(items):
"""Hitung subtotal pesanan (penggunaan internal)."""
total = 0
for item in items:
total += item["price"] * item["quantity"]
return total
def _calculate_discount(subtotal, customer):
"""Hitung diskon pelanggan (penggunaan internal)."""
if customer.get("is_premium"):
return subtotal * 0.1
return 0
def _calculate_tax(amount):
"""Hitung pajak penjualan (penggunaan internal)."""
TAX_RATE = 0.08
return amount * TAX_RATE
def _validate_item(item):
"""Validasi satu item pesanan (penggunaan internal)."""
required_fields = ["name", "price", "quantity"]
return all(field in item for field in required_fields)Perhatikan bagaimana fungsi publik (process_order, validate_order) diletakkan di awal, dan fungsi helper (diawali _) diletakkan setelahnya. Ini membuat jelas fungsi mana yang menjadi API utama.
40.5.5) Organisasi Class di Dalam Modul
Saat sebuah modul berisi class, atur mereka secara logis:
"""Sistem manajemen pengguna."""
# Konstanta
DEFAULT_ROLE = "user"
ADMIN_ROLE = "admin"
# Base class dulu
class User:
"""Class user dasar."""
def __init__(self, username, email):
self.username = username
self.email = email
self.role = DEFAULT_ROLE
def can_edit(self, resource):
"""Periksa apakah user dapat mengedit sebuah resource."""
return resource.owner == self.username
# Derived class setelah base class
class AdminUser(User):
"""Administrator dengan hak istimewa lebih tinggi."""
def __init__(self, username, email):
super().__init__(username, email)
self.role = ADMIN_ROLE
def can_edit(self, resource):
"""Admin dapat mengedit resource apa pun."""
return True
# Class terkait dikelompokkan bersama
class Resource:
"""Merepresentasikan sebuah resource yang bisa dimiliki dan diedit."""
def __init__(self, name, owner):
self.name = name
self.owner = owner
# Fungsi utilitas terkait class
def create_user(username, email, is_admin=False):
"""Factory function untuk membuat tipe user yang sesuai."""
if is_admin:
return AdminUser(username, email)
return User(username, email)Prinsip organisasi class:
- Base class sebelum derived class (pembaca perlu memahami base dulu)
- Class yang terkait dikelompokkan bersama (User dan Resource terkait)
- Fungsi utilitas yang bekerja dengan class diletakkan setelah definisi class
- Setiap class harus punya docstring yang jelas menjelaskan tujuannya
40.6) Pola if name == "main"
40.6.1) Memahami Polanya
Setiap file Python memiliki variabel bawaan bernama __name__. Python secara otomatis menetapkan nilai variabel ini tergantung bagaimana file tersebut digunakan:
- Saat kamu menjalankan file secara langsung (misalnya,
python my_script.py), Python menetapkan__name__menjadi"__main__" - Saat kamu mengimpor file sebagai modul, Python menetapkan
__name__menjadi nama modul (nama file tanpa.py)
Ini memungkinkan kamu menulis kode yang hanya berjalan saat file dieksekusi langsung, bukan saat diimpor:
"""math_utils.py - Fungsi utilitas matematika."""
def add(a, b):
"""Tambah dua angka."""
return a + b
def multiply(a, b):
"""Kalikan dua angka."""
return a * b
# Kode ini hanya berjalan ketika file dieksekusi langsung
if __name__ == "__main__":
# Uji fungsi
print(f"5 + 3 = {add(5, 3)}") # Output: 5 + 3 = 8
print(f"5 * 3 = {multiply(5, 3)}") # Output: 5 * 3 = 15Saat kamu menjalankan python math_utils.py, kamu akan melihat outputnya. Tetapi saat kamu mengimpornya di file lain:
# another_file.py
from math_utils import add, multiply
result = add(10, 20)
print(result) # Output: 30
# Kode test dari math_utils.py TIDAK berjalanPerhatikan bahwa kode test (di dalam if __name__ == "__main__":) TIDAK berjalan saat diimpor!
40.6.2) Kenapa Pola Ini Penting
Pola ini punya beberapa tujuan penting:
1. Testing dan demonstrasi: Kamu bisa menyertakan contoh penggunaan di file yang sama dengan fungsi kamu:
"""temperature.py - Utilitas konversi suhu."""
def celsius_to_fahrenheit(celsius):
"""Konversi Celsius ke Fahrenheit."""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit):
"""Konversi Fahrenheit ke Celsius."""
return (fahrenheit - 32) * 5/9
if __name__ == "__main__":
# Demonstrasikan fungsi
print("Temperature Conversion Examples:")
print(f"0°C = {celsius_to_fahrenheit(0)}°F") # Output: 0°C = 32.0°F
print(f"100°C = {celsius_to_fahrenheit(100)}°F") # Output: 100°C = 212.0°F
print(f"32°F = {fahrenheit_to_celsius(32)}°C") # Output: 32°F = 0.0°C2. Modul yang bisa digunakan ulang: File yang sama bisa sekaligus menjadi script mandiri dan modul yang bisa diimpor:
"""data_processor.py - Memproses dan menganalisis file data."""
import sys
def load_data(filename):
"""Muat data dari sebuah file."""
with open(filename) as f:
return [line.strip() for line in f]
def analyze_data(data):
"""Lakukan analisis pada data."""
return {
"count": len(data),
"average_length": sum(len(item) for item in data) / len(data)
}
if __name__ == "__main__":
# Saat dijalankan sebagai script, proses argumen command-line
if len(sys.argv) < 2:
print("Usage: python data_processor.py <filename>")
sys.exit(1)
filename = sys.argv[1]
data = load_data(filename)
results = analyze_data(data)
print(f"Processed {results['count']} items")
print(f"Average length: {results['average_length']:.2f}")Kamu bisa menjalankannya sebagai script:
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23Atau mengimpornya di file lain:
# my_analysis.py
from data_processor import load_data, analyze_data
my_data = load_data("myfile.txt")
results = analyze_data(my_data)
print(f"Found {results['count']} items")40.6.3) Pola Umum untuk Blok Main
Pola 1: Test case sederhana
"""calculator.py - Operasi kalkulator dasar."""
def add(a, b):
"""Tambah dua angka."""
return a + b
def subtract(a, b):
"""Kurangi b dari a."""
return a - b
if __name__ == "__main__":
# Test cepat
assert add(2, 3) == 5
assert subtract(10, 4) == 6
print("All tests passed!") # Output: All tests passed!Pola 2: Fungsi main
Untuk script yang lebih kompleks, definisikan fungsi main():
"""report_generator.py - Menghasilkan laporan dari data."""
import sys
def load_data(filename):
"""Muat data dari file."""
# Implementasi di sini
pass
def generate_report(data):
"""Buat laporan dari data."""
# Implementasi di sini
pass
def save_report(report, output_file):
"""Simpan laporan ke file."""
# Implementasi di sini
pass
def main():
"""Entry point utama untuk script."""
if len(sys.argv) < 3:
print("Usage: python report_generator.py <input> <output>")
return 1
input_file = sys.argv[1]
output_file = sys.argv[2]
try:
data = load_data(input_file)
report = generate_report(data)
save_report(report, output_file)
print(f"Report saved to {output_file}")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
# Keluar dengan status code dari main (0 = sukses, 1 = error)
sys.exit(main())Pola ini punya beberapa keuntungan:
- Fungsi
main()bisa diuji secara terpisah - Entry point yang jelas untuk script
- Exit code yang tepat (0 untuk sukses, non-zero untuk error)
- Pemisahan yang bersih antara logika script dan fungsi modul
40.6.4) Praktik Terbaik untuk Blok Main
Jaga blok main tetap fokus: Kode di dalam if __name__ == "__main__" seharusnya terutama menangani eksekusi script, bukan berisi logika yang kompleks:
# Buruk - logika kompleks di blok main
# PERINGATAN: Gaya buruk - hanya untuk demonstrasi
if __name__ == "__main__":
data = []
for i in range(100):
if i % 2 == 0:
data.append(i * 2)
result = sum(data) / len(data)
print(result)
# Baik - logika ada di fungsi, blok main mengoordinasikan
def generate_even_doubles(limit):
"""Hasilkan bilangan genap yang digandakan hingga limit."""
return [i * 2 for i in range(limit) if i % 2 == 0]
def calculate_average(numbers):
"""Hitung rata-rata dari numbers."""
return sum(numbers) / len(numbers)
if __name__ == "__main__":
data = generate_even_doubles(100)
result = calculate_average(data)
print(result) # Output: 99.0Gunakan fungsi main() untuk script yang kompleks: Seperti ditunjukkan sebelumnya, mendefinisikan fungsi main() membuat script kamu lebih mudah diuji dan terorganisasi.
Dokumentasikan penggunaan script: Jika script kamu menerima argumen command-line, dokumentasikan di docstring modul:
"""
file_processor.py - Memproses file teks dengan berbagai operasi.
Usage:
python file_processor.py <input_file> <output_file> [--uppercase]
Arguments:
input_file: Path ke file input
output_file: Path ke file output
--uppercase: Ubah teks menjadi huruf besar (opsional)
"""
import sys
def process_file(input_path, output_path, uppercase=False):
"""Proses file dengan opsi yang ditentukan."""
with open(input_path) as f:
content = f.read()
if uppercase:
content = content.upper()
with open(output_path, 'w') as f:
f.write(content)
if __name__ == "__main__":
if len(sys.argv) < 3:
print(__doc__) # Cetak docstring modul
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
uppercase = "--uppercase" in sys.argv
process_file(input_file, output_file, uppercase)
print(f"Processed {input_file} -> {output_file}")Menulis kode yang bersih dan mudah dibaca adalah keterampilan yang berkembang lewat latihan. Konvensi dan pola di bab ini bukan aturan yang arbitrer—ini praktik yang sudah terbukti membuat kode lebih mudah dipahami, dipelihara, dan di-debug. Saat kamu menulis lebih banyak kode Python, pola-pola ini akan menjadi kebiasaan.
Ingat: kode jauh lebih sering dibaca daripada ditulis. Beberapa detik ekstra yang kamu habiskan untuk memilih nama yang jelas, menambahkan komentar yang membantu, atau mengatur import dengan benar akan menghemat berjam-jam kebingungan nanti—untuk dirimu sendiri dan orang lain yang bekerja dengan kode kamu.
Di bab berikutnya, kita akan mengeksplorasi teknik debugging dan testing yang dibangun di atas praktik clean code ini, membantu kamu menulis bukan hanya kode yang mudah dibaca, tetapi juga kode yang benar dan andal.