Python & AI Tutorials Logo
Pemrograman Python

26. Teknik Pemrograman Defensif Menggunakan Exception dan Validasi

Pemrograman defensif (defensive programming) berarti menulis kode yang mengantisipasi masalah sebelum terjadi. Alih-alih menganggap semuanya akan berjalan sempurna, kode defensif memvalidasi input, menangani error dengan baik, dan memeriksa asumsi. Pendekatan ini menghasilkan program yang lebih andal, lebih mudah di-debug, dan lebih kecil kemungkinannya crash secara tidak terduga.

Di bab sebelumnya, kita belajar cara menangani exception saat exception terjadi. Sekarang kita akan belajar cara mencegah banyak error terjadi sejak awal, dan cara menangkap masalah lebih dini ketika memang terjadi.

26.1) Memvalidasi Argumen Fungsi

Fungsi(function) sering menerima data dari bagian lain program Anda atau dari pengguna. Jika sebuah fungsi menerima data yang tidak valid, fungsi itu bisa menghasilkan hasil yang salah, crash dengan error yang membingungkan, atau menyebabkan masalah di bagian lain program Anda. Validasi argumen (argument validation) berarti memeriksa bahwa argumen fungsi memenuhi kebutuhan Anda sebelum menggunakannya.

26.1.1) Mengapa Memvalidasi Argumen?

Pertimbangkan fungsi ini yang menghitung persentase nilai seorang siswa:

python
def calculate_percentage(points_earned, total_points):
    return (points_earned / total_points) * 100
 
# Menggunakan fungsi
percentage = calculate_percentage(85, 100)
print(f"Grade: {percentage}%")  # Output: Grade: 85.0%

Ini bekerja baik dengan input yang valid. Tapi apa yang terjadi dengan data bermasalah?

python
# Masalah 1: Pembagian dengan nol
percentage = calculate_percentage(85, 0)  # ZeroDivisionError!
 
# Masalah 2: Nilai negatif (tidak masuk akal)
percentage = calculate_percentage(-10, 100)  # -10.0%
 
# Masalah 3: Poin yang didapat melebihi total (mustahil)
percentage = calculate_percentage(120, 100)  # 120.0%

Tanpa validasi, fungsi tersebut bisa crash atau menghasilkan hasil yang tidak masuk akal. Pesan error tidak menjelaskan apa yang salah dari perspektif logika bisnis—mereka hanya menunjukkan kegagalan teknis.

26.1.2) Validasi Argumen Dasar dengan Kondisional

Pendekatan validasi paling sederhana menggunakan statement if untuk memeriksa argumen dan me-raise exception saat argumen tidak valid:

python
def calculate_percentage(points_earned, total_points):
    # Validasi total_points
    if total_points <= 0:
        raise ValueError("total_points must be positive")
    
    # Validasi points_earned
    if points_earned < 0:
        raise ValueError("points_earned cannot be negative")
    
    if points_earned > total_points:
        raise ValueError("points_earned cannot exceed total_points")
    
    # Semua validasi lolos - aman untuk menghitung
    return (points_earned / total_points) * 100
 
# Penggunaan valid
percentage = calculate_percentage(85, 100)
print(f"Grade: {percentage}%")  # Output: Grade: 85.0%
 
# Penggunaan tidak valid - pesan error jelas
try:
    percentage = calculate_percentage(85, 0)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: total_points must be positive
 
try:
    percentage = calculate_percentage(-10, 100)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: points_earned cannot be negative
 
try:
    percentage = calculate_percentage(120, 100)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: points_earned cannot exceed total_points

Sekarang ketika ada sesuatu yang salah, pesan error menjelaskan dengan jelas apa masalahnya dan bagaimana cara memperbaikinya.

26.1.3) Memvalidasi Tipe Argumen

Terkadang Anda perlu memastikan argumen memiliki tipe yang benar:

python
def calculate_discount(price, discount_percent):
    # Validasi tipe
    if not isinstance(price, (int, float)):
        raise TypeError("price must be a number")
    
    if not isinstance(discount_percent, (int, float)):
        raise TypeError("discount_percent must be a number")
    
    # Validasi nilai
    if price < 0:
        raise ValueError("price cannot be negative")
    
    if not (0 <= discount_percent <= 100):
        raise ValueError("discount_percent must be between 0 and 100")
    
    # Hitung diskon
    discount_amount = price * (discount_percent / 100)
    return price - discount_amount
 
# Penggunaan valid
final_price = calculate_discount(50.00, 20)
print(f"Final price: ${final_price:.2f}")  # Output: Final price: $40.00
 
# Error tipe
try:
    final_price = calculate_discount("50", 20)
except TypeError as e:
    print(f"Error: {e}")  # Output: Error: price must be a number
 
# Error nilai
try:
    final_price = calculate_discount(50.00, 150)
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: discount_percent must be between 0 and 100

Fungsi isinstance() memeriksa apakah sebuah objek adalah instance dari tipe tertentu atau beberapa tipe. Kita memberikan tuple (int, float) agar menerima integer maupun float, karena keduanya adalah tipe numerik yang valid untuk harga.

Kapan memvalidasi tipe: Filosofi Python adalah "duck typing"—kalau sebuah objek berperilaku seperti yang Anda butuhkan, pakai saja. Validasi tipe paling berguna ketika:

  • Anda menulis fungsi yang akan digunakan oleh orang lain
  • Error tipe akan menyebabkan kegagalan yang membingungkan di tahap berikutnya
  • Fungsi tersebut adalah bagian dari API publik atau library

26.1.4) Memvalidasi Argumen Koleksi

Saat fungsi menerima list, dictionary, atau koleksi lainnya, validasi baik koleksinya maupun isinya:

python
def calculate_average_grade(grades):
    # Validasi koleksinya sendiri
    if not isinstance(grades, list):
        raise TypeError("grades must be a list")
    
    if len(grades) == 0:
        raise ValueError("grades list cannot be empty")
    
    # Validasi setiap grade di dalam koleksi
    for i, grade in enumerate(grades):
        if not isinstance(grade, (int, float)):
            raise TypeError(f"grade at index {i} must be a number, got {type(grade).__name__}")
        
        if not (0 <= grade <= 100):
            raise ValueError(f"grade at index {i} must be between 0 and 100, got {grade}")
    
    # Semua validasi lolos
    return sum(grades) / len(grades)
 
# Penggunaan valid
grades = [85, 92, 78, 95]
average = calculate_average_grade(grades)
print(f"Average: {average:.1f}")  # Output: Average: 87.5
 
# Error list kosong
try:
    average = calculate_average_grade([])
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: grades list cannot be empty
 
# Tipe grade tidak valid
try:
    average = calculate_average_grade([85, "92", 78])
except TypeError as e:
    print(f"Error: {e}")  # Output: Error: grade at index 1 must be a number, got str
 
# Nilai grade tidak valid
try:
    average = calculate_average_grade([85, 92, 150])
except ValueError as e:
    print(f"Error: {e}")  # Output: Error: grade at index 2 must be between 0 and 100, got 150

Perhatikan bagaimana kita menyertakan indeks dalam pesan error ketika memvalidasi elemen koleksi. Ini membantu mengidentifikasi item mana yang bermasalah, terutama pada koleksi yang besar.

Tipe Tidak Valid

Nilai Tidak Valid

Valid

Fungsi Dipanggil

Validasi
Argumen

Raise TypeError

Raise ValueError

Jalankan Logika Fungsi

Kembalikan Hasil

Pemanggil Menangani Exception

26.2) Memeriksa Input Pengguna agar Valid

Input pengguna pada dasarnya tidak dapat diandalkan—pengguna melakukan typo, salah paham instruksi, atau memasukkan data dalam format yang tidak terduga. Memvalidasi input pengguna mencegah kesalahan ini menyebabkan program crash atau menghasilkan hasil yang salah.

26.2.1) Pola Dasar Validasi Input

Pola dasar untuk validasi input menggabungkan input() dengan pemeriksaan validasi:

python
# Ambil input pengguna
age_str = input("Enter your age: ")
 
# Validasi input
try:
    age = int(age_str)
    if age < 0:
        print("Error: Age cannot be negative")
    elif age > 150:
        print("Error: Age seems unrealistic")
    else:
        print(f"You are {age} years old")
except ValueError:
    print("Error: Please enter a valid number")

Pola ini punya tiga bagian:

  1. Ambil input sebagai string
  2. Coba konversi ke tipe yang dibutuhkan
  3. Periksa apakah nilai hasil konversi valid

Mari lihat polanya beraksi dengan input yang berbeda:

python
# Input valid
# User enters: 25
# Output: You are 25 years old
 
# Tipe tidak valid
# User enters: twenty-five
# Output: Error: Please enter a valid number
 
# Nilai tidak valid (negatif)
# User enters: -5
# Output: Error: Age cannot be negative
 
# Nilai tidak valid (tidak realistis)
# User enters: 200
# Output: Error: Age seems unrealistic

26.2.2) Memvalidasi Rentang dan Format Input

Beberapa input harus berada dalam rentang tertentu atau cocok dengan format tertentu:

python
# Memvalidasi bulan (1-12)
month_str = input("Enter month (1-12): ")
try:
    month = int(month_str)
    if not (1 <= month <= 12):
        print("Error: Month must be between 1 and 12")
    else:
        print(f"Month: {month}")
except ValueError:
    print("Error: Please enter a whole number")
 
# Memvalidasi format email (cek sederhana)
email = input("Enter email: ")
if '@' not in email or '.' not in email:
    print("Error: Email must contain @ and .")
else:
    print(f"Email: {email}")
 
# Memvalidasi input ya/tidak
response = input("Continue? (yes/no): ").lower().strip()
if response not in ['yes', 'no', 'y', 'n']:
    print("Error: Please answer yes or no")
else:
    if response in ['yes', 'y']:
        print("Continuing...")
    else:
        print("Stopping...")

Validasi email di sini sengaja dibuat sederhana—hanya memeriksa struktur dasarnya saja. Validasi email yang sebenarnya jauh lebih kompleks dan biasanya menggunakan regular expression (yang akan kita pelajari di Bab 39).

26.2.3) Memberikan Pesan Error yang Membantu

Pesan error yang baik memberi tahu pengguna dengan tepat apa yang salah dan bagaimana cara memperbaikinya:

python
# Pesan error yang buruk
password = input("Enter password: ")
if len(password) < 8:
    print("Error: Invalid password")  # Tidak membantu!
 
# Pesan error yang lebih baik
password = input("Enter password: ")
if len(password) < 8:
    print("Error: Password must be at least 8 characters long")
    print(f"Your password is only {len(password)} characters")
 
# Lebih baik lagi - jelaskan semua kebutuhan di awal
print("Password requirements:")
print("- At least 8 characters")
print("- Must contain at least one number")
password = input("Enter password: ")
 
# Cek panjang
if len(password) < 8:
    print(f"Error: Password too short ({len(password)} characters)")
    print("Password must be at least 8 characters")
# Cek digit
elif not any(char.isdigit() for char in password):
    print("Error: Password must contain at least one number")
else:
    print("Password accepted")

Fungsi any() mengembalikan True jika ada elemen dalam sebuah iterable yang bernilai true. Di sini, char.isdigit() memeriksa apakah setiap karakter adalah digit, dan any() memberi tahu kita apakah setidaknya ada satu karakter yang lolos tes.

Konversi Gagal

Konversi Berhasil

Di Luar Rentang

Format Tidak Valid

Valid

Ambil Input Pengguna

Coba Konversi Tipe

ValueError:
Format Tidak Valid

Cek Batasan
Nilai

Value Error:
Pesan Jelas

Format Error:
Pesan Jelas

Gunakan Input

Tampilkan Error,
Jelaskan Format yang Diharapkan

26.3) Menggabungkan input(), Loop, dan try/except untuk Penanganan Input yang Tangguh

Pemeriksaan validasi satu kali itu berguna, tetapi tidak menangani kesalahan pengguna yang terus-menerus. Jika pengguna memasukkan data yang tidak valid, program Anda seharusnya memberi mereka kesempatan lain. Menggabungkan loop dengan validasi menghasilkan penanganan input yang tangguh yang terus bertanya sampai mendapatkan data yang valid.

26.3.1) Pola Loop Input Dasar

Pola dasarnya menggunakan loop while yang berlanjut sampai input valid diterima:

python
# Terus bertanya sampai kita mendapatkan umur yang valid
while True:
    age_str = input("Enter your age: ")
    try:
        age = int(age_str)
        if age < 0:
            print("Error: Age cannot be negative. Please try again.")
        elif age > 150:
            print("Error: Age seems unrealistic. Please try again.")
        else:
            # Input valid - keluar dari loop
            break
    except ValueError:
        print("Error: Please enter a valid number.")
 
print(f"You are {age} years old")

Pola ini punya beberapa elemen kunci:

  • while True: membuat loop tak hingga
  • Validasi terjadi di dalam loop
  • break keluar dari loop ketika input valid
  • Pesan error mendorong pengguna untuk mencoba lagi

Mari lihat bagaimana ini menangani berbagai input:

python
# Contoh interaksi:
# Enter your age: twenty
# Error: Please enter a valid number.
# Enter your age: -5
# Error: Age cannot be negative. Please try again.
# Enter your age: 25
# You are 25 years old

26.3.2) Membuat Fungsi Input yang Dapat Dipakai Ulang

Ketika Anda membutuhkan jenis input tervalidasi yang sama di banyak tempat, buatlah sebuah fungsi:

python
def get_positive_integer(prompt):
    """Terus bertanya sampai pengguna memasukkan integer positif."""
    while True:
        try:
            value = int(input(prompt))
            if value <= 0:
                print("Error: Please enter a positive number.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid whole number.")
 
def get_number_in_range(prompt, min_value, max_value):
    """Terus bertanya sampai pengguna memasukkan angka dalam rentang yang ditentukan."""
    while True:
        try:
            value = float(input(prompt))
            if value < min_value or value > max_value:
                print(f"Error: Please enter a number between {min_value} and {max_value}.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid number.")
 
# Menggunakan fungsi
quantity = get_positive_integer("Enter quantity: ")
print(f"Quantity: {quantity}")
 
grade = get_number_in_range("Enter grade (0-100): ", 0, 100)
print(f"Grade: {grade}")
 
temperature = get_number_in_range("Enter temperature (-50 to 50): ", -50, 50)
print(f"Temperature: {temperature}°C")

Fungsi-fungsi ini mengenkapsulasi logika validasi, sehingga kode utama Anda lebih bersih dan lebih mudah dibaca. Mereka juga memastikan perilaku validasi yang konsisten di seluruh program Anda.

26.4) Menggunakan Assertion untuk Pemeriksaan Invarian saat Pengembangan

Assertion adalah jenis pemeriksaan khusus yang digunakan saat pengembangan untuk memverifikasi bahwa asumsi kode Anda benar. Berbeda dengan validasi (yang menangani error yang diharapkan dari pengguna atau data eksternal), assertion menangkap kesalahan pemrograman—situasi yang seharusnya tidak pernah terjadi jika kode Anda benar.

26.4.1) Apa Itu Assertion dan Kapan Menggunakannya

Sebuah assertion adalah pernyataan yang seharusnya selalu benar pada titik tertentu dalam kode Anda. Jika salah, berarti ada sesuatu yang secara fundamental bermasalah dengan logika program Anda:

python
def calculate_average(numbers):
    # Ini seharusnya tidak pernah terjadi jika fungsi dipanggil dengan benar
    assert len(numbers) > 0, "numbers list cannot be empty"
    
    return sum(numbers) / len(numbers)
 
# Penggunaan benar
grades = [85, 90, 78]
average = calculate_average(grades)
print(f"Average: {average:.1f}")  # Output: Average: 84.3
 
# Penggunaan salah - memicu assertion
empty_list = []
average = calculate_average(empty_list)  # AssertionError: numbers list cannot be empty

Ketika sebuah assertion gagal, Python me-raise AssertionError dengan pesan Anda. Ini langsung menghentikan program dan menunjukkan tepat di mana asumsi Anda dilanggar.

Perbedaan utama:

  • Validasi (menggunakan if dan raise): Untuk menangani masalah yang diharapkan dari pengguna atau data eksternal
  • Assertion: Untuk menangkap bug pemrograman selama pengembangan
python
# Validasi - menangani error pengguna yang diharapkan
def get_positive_number(prompt):
    while True:
        try:
            value = float(input(prompt))
            if value <= 0:
                print("Error: Please enter a positive number.")
            else:
                return value
        except ValueError:
            print("Error: Please enter a valid number.")
 
# Assertion - menangkap kesalahan pemrograman
def calculate_discount(price, discount_rate):
    # Ini seharusnya tidak pernah dilanggar jika program ditulis dengan benar
    assert price >= 0, "price should be non-negative"
    assert 0 <= discount_rate <= 1, "discount_rate should be between 0 and 1"
    
    return price * (1 - discount_rate)

26.4.2) Memeriksa Prasyarat Fungsi

Assertion sangat bagus untuk memverifikasi bahwa prasyarat (preconditions) sebuah fungsi (persyaratan yang harus benar sebelum fungsi dieksekusi) terpenuhi:

python
def get_list_element(items, index):
    """Ambil sebuah elemen dari list pada indeks yang ditentukan."""
    # Prasyarat
    assert isinstance(items, list), "items must be a list"
    assert isinstance(index, int), "index must be an integer"
    assert 0 <= index < len(items), f"index {index} out of range for list of length {len(items)}"
    
    return items[index]
 
# Penggunaan benar
numbers = [10, 20, 30, 40]
value = get_list_element(numbers, 2)
print(f"Value: {value}")  # Output: Value: 30
 
# Error pemrograman - tipe salah
value = get_list_element("not a list", 0)  # AssertionError: items must be a list
 
# Error pemrograman - indeks tidak valid
value = get_list_element(numbers, 10)  # AssertionError: index 10 out of range for list of length 4

Assertion ini membantu menangkap bug saat pengembangan. Jika Anda tidak sengaja mengirim tipe yang salah atau indeks yang tidak valid, assertion langsung memberi tahu apa yang salah.

26.4.3) Memeriksa Pascasyarat Fungsi

Pascasyarat (postconditions) adalah kondisi yang harus benar setelah sebuah fungsi dieksekusi. Assertion dapat memverifikasi bahwa fungsi Anda menghasilkan hasil yang valid:

python
def calculate_percentage(part, whole):
    """Hitung berapa persen 'part' dari 'whole'."""
    # Prasyarat
    assert whole > 0, "whole must be positive"
    assert part >= 0, "part must be non-negative"
    
    # Hitung persentase
    percentage = (part / whole) * 100
    
    # Pascasyarat - hasil harus berupa persentase yang valid
    assert 0 <= percentage <= 100, f"percentage {percentage} is outside valid range"
    
    return percentage
 
# Ini bekerja dengan benar
percentage = calculate_percentage(25, 100)
print(f"Percentage: {percentage}%")  # Output: Percentage: 25.0%
 
# Ini mengungkap error logika dalam fungsi kita
# (kita tidak memeriksa bahwa part <= whole)
percentage = calculate_percentage(150, 100)  # AssertionError: percentage 150.0 is outside valid range

Assertion pascasyarat menangkap bug dalam fungsi kita—kita lupa memvalidasi bahwa part tidak melebihi whole. Inilah tepatnya kegunaan assertion: menangkap kesalahan pemrograman.

26.4.4) Assertion Bisa Dinonaktifkan

Karakteristik penting dari assertion adalah bahwa assertion dapat dinonaktifkan saat menjalankan Python dengan flag -O (optimize):

python
# File ini bernama test_assertions.py
def divide(a, b):
    assert b != 0, "divisor cannot be zero"
    return a / b
 
result = divide(10, 2)
print(f"Result: {result}")
 
result = divide(10, 0)  # AssertionError when assertions are enabled

Menjalankan secara normal:

bash
python test_assertions.py
# Output: Result: 5.0
# Then: AssertionError: divisor cannot be zero

Menjalankan dengan optimisasi:

bash
python -O test_assertions.py
# Output: Result: 5.0
# Then: ZeroDivisionError: division by zero

Inilah mengapa assertion sebaiknya tidak digunakan untuk validasi data eksternal—jika seseorang menjalankan program Anda dengan -O, semua assertion dilewati. Gunakan assertion hanya untuk menangkap bug pemrograman saat pengembangan dan pengujian.

Kondisi True

Kondisi False

Eksekusi Kode

Pemeriksaan Assertion

Lanjutkan Eksekusi

Raise AssertionError
dengan Pesan

Program Berhenti
Menampilkan Traceback

Developer Memperbaiki Bug

© 2025. Primesoft Co., Ltd.
support@primesoft.ai