08 — Error Handling & Debugging
Estimasi: 4 jam Tujuan: Handle exception dengan elegan, dan debug masalah dengan sistematis. Skill yang dipakai sepanjang karir.
Kenapa Materi Ini Penting?
ML practitioner habiskan lebih banyak waktu untuk debug daripada nulis kode baru. Model crash karena tensor shape mismatch, training stop karena GPU OOM, prediction salah karena edge case di data. Tanpa skill error handling yang matang, kamu akan stuck berjam-jam di error yang sebenarnya bisa di-handle dalam 5 menit.
Lebih dari sekadar "bungkus dengan try/except", error handling profesional itu soal mendesain failure mode: kapan retry, kapan gracefully degrade, kapan log dan alert, kapan crash loud dan jelas. Production ML system tanpa error handling proper = bom waktu.
Analogi besar: error handling = safety net di sirkus. Kamu tetap mau atraksi yang berani (kode yang ambisius), tapi siapkan jaring kalau jatuh. Tanpa jaring, satu fall = game over. Dengan jaring, kamu bisa coba lagi.
Peta Konsep
flowchart TD
A[⚠️ Error Handling] --> B[🚨 Exception]
A --> C[🛡️ try/except]
A --> D[🔄 raise]
A --> E[🎯 Custom Exception]
A --> F[🔍 Debugging]
C --> C1[try]
C --> C2[except]
C --> C3[else]
C --> C4[finally]
F --> F1[Traceback]
F --> F2[print debug]
F --> F3[logging]
F --> F4[pdb / debugger]
Diagram Alur try/except/else/finally
Cara Membaca Diagram
Flow kiri-ke-kanan dengan 2 jalur utama: sukses (atas, hijau) dan error (bawah, pink). Kedua jalur akhirnya converge ke finally. Node finally dijamin selalu dijalankan — itulah fungsinya untuk cleanup.
Walkthrough Step-by-Step
- Mulai — masuk try block.
- try block — eksekusi kode berisiko (parse input, bagi angka, akses API).
- Cek Error?
- Tidak → ke else block (jalan kalau sukses).
- Ya → ke check match except.
- Match except?
- Ya → eksekusi handler (
except ValueError: ...). Error ditangani. - Tidak → exception propagate naik ke caller.
- Ya → eksekusi handler (
- finally — selalu dijalankan, sukses maupun gagal. Cocok untuk cleanup (close file, release lock).
- End — kalau handler success, lanjut program. Kalau propagate, exception naik ke level atas.
Analogi Sehari-hari
Try/except = safety net di sirkus. try = atraksi berani (lompat antar gantungan). except = jaring bawah yang siap kalau jatuh. else = perayaan saat sukses landing. finally = bersih-bersih panggung — apapun hasilnya (sukses atau jatuh), panggung harus dibersihkan. Tanpa jaring, satu jatuh = game over. Dengan jaring, kamu bisa coba lagi.
Diagram statis Mermaid sebagai fallback:
flowchart TD
Start([Mulai]) --> T[try block]
T --> Q{Error?}
Q -->|Tidak| EL[else block]
Q -->|Ya| EX{except match?}
EX -->|Ya| H[handle exception]
EX -->|Tidak| RE[propagate exception]
H --> F[finally block]
EL --> F
RE --> F
F --> End([Lanjut / Re-raise])
Analogi:
- try = "coba ini"
- except = "kalau gagal, lakukan ini"
- else = "kalau sukses, lakukan ini juga"
- finally = "tidak peduli sukses/gagal, lakukan ini di akhir" (tutup file, release resource)
Bagian 1 — Apa Itu Exception?
Exception = error saat runtime yang menghentikan program.
print(10 / 0)
# ZeroDivisionError: division by zero
data = [1, 2, 3]
print(data[10])
# IndexError: list index out of range
Tanpa handling, program crash. Dengan handling, program bisa lanjut atau gracefully fail.
Built-in Exception Penting
| Exception | Kapan |
|---|---|
ValueError |
Tipe benar, value salah (int("abc")) |
TypeError |
Tipe salah ("a" + 1) |
KeyError |
Key tidak ada di dict |
IndexError |
Index di luar range |
FileNotFoundError |
File tidak ada |
ZeroDivisionError |
Bagi 0 |
AttributeError |
Method/attribute tidak ada |
ImportError |
Gagal import |
NameError |
Variabel tidak terdefinisi |
RuntimeError |
Generic runtime error |
Exception |
Base class semua |
Bagian 2 — try/except
Bentuk Dasar
try:
angka = int(input("Masukkan angka: "))
print(f"Kuadrat: {angka ** 2}")
except ValueError:
print("Input bukan angka!")
Kalau int() gagal → loncat ke except. Program tidak crash.
Multiple Except
try:
angka = int(input("Angka: "))
hasil = 100 / angka
except ValueError:
print("Bukan angka!")
except ZeroDivisionError:
print("Tidak boleh 0!")
Except Multiple Sekaligus
try:
# ...
except (ValueError, TypeError) as e:
print(f"Error: {e}")
Catch-all (Hindari!)
# ❌ Buruk — sembunyikan semua bug
try:
do_something()
except:
pass
# ✅ Lebih baik — minimal log
try:
do_something()
except Exception as e:
print(f"Error: {e}")
raise # re-raise kalau memang tidak bisa di-handle
Aturan: HINDARI bare
except:danexcept Exception:tanpa handling spesifik. Itu menyembunyikan bug.
else — Jalan Kalau Tidak Ada Error
try:
angka = int(input("Angka: "))
except ValueError:
print("Bukan angka!")
else:
print(f"Kuadrat: {angka ** 2}") # hanya jalan kalau try sukses
finally — Selalu Jalan
try:
f = open("data.txt")
# ...
except FileNotFoundError:
print("File tidak ada")
finally:
f.close() # selalu jalan, walau ada error atau tidak
Modern alternative: pakai
withstatement untuk context manager. Lebih bersih.
Bagian 3 — Raise Exception
Bikin error sendiri:
def hitung_bmi(berat, tinggi):
if berat <= 0:
raise ValueError("Berat harus positif")
if tinggi <= 0:
raise ValueError("Tinggi harus positif")
return berat / (tinggi ** 2)
hitung_bmi(-70, 1.7)
# ValueError: Berat harus positif
Re-raise
try:
do_something()
except ValueError as e:
log_error(e)
raise # re-raise yang sama
# Atau dengan context tambahan
try:
do_something()
except ValueError as e:
raise RuntimeError(f"Gagal di step X: {e}") from e
from e mempertahankan original exception trace — penting untuk debugging.
Bagian 4 — Custom Exception
Diagram: Hierarki Exception
classDiagram
class Exception
class BankError {
+str message
}
class InsufficientBalanceError
class InvalidAmountError
class AccountFrozenError
Exception <|-- BankError
BankError <|-- InsufficientBalanceError
BankError <|-- InvalidAmountError
BankError <|-- AccountFrozenError
Analogi: custom exception = kategori error sesuai domain bisnis. Bank punya "saldo kurang", e-commerce punya "barang habis", auth punya "token expired". Kategori yang spesifik bikin handling lebih clean.
Bikin exception class sendiri untuk domain spesifik:
class InsufficientBalanceError(Exception):
"""Saldo tidak cukup."""
pass
class InvalidAmountError(Exception):
"""Jumlah tidak valid."""
pass
class BankAccount:
def __init__(self, saldo=0):
self.saldo = saldo
def tarik(self, jumlah):
if jumlah <= 0:
raise InvalidAmountError(f"Jumlah harus positif, got {jumlah}")
if jumlah > self.saldo:
raise InsufficientBalanceError(
f"Saldo {self.saldo} kurang dari {jumlah}"
)
self.saldo -= jumlah
# Pakai
try:
acc = BankAccount(1000)
acc.tarik(2000)
except InsufficientBalanceError as e:
print(f"Saldo kurang: {e}")
except InvalidAmountError as e:
print(f"Jumlah error: {e}")
Best practice: custom exception membuat error handling lebih clear. Tidak perlu untuk script kecil. Wajib untuk library.
Bagian 5 — EAFP vs LBYL (Pythonic Style)
Dua filosofi handling:
LBYL = Look Before You Leap (cek dulu sebelum aksi)
if "key" in d:
value = d["key"]
else:
value = default
EAFP = Easier to Ask Forgiveness than Permission (coba aja, handle kalau gagal)
try:
value = d["key"]
except KeyError:
value = default
Python lebih suka EAFP. Tapi banyak shortcut Pythonic:
# Lebih Pythonic
value = d.get("key", default)
# Untuk check file
from pathlib import Path
if Path("file.txt").exists():
pass
Pilih yang lebih bersih per kasus. Yang penting konsisten dalam satu codebase.
Bagian 6 — Debugging
Saat error muncul, baca traceback dari bawah ke atas (kebanyakan kasus):
Traceback (most recent call last):
File "main.py", line 10, in <module>
process(data)
File "main.py", line 6, in process
return data["nama"]
KeyError: 'nama'
- Bottom: error type + message (paling info)
- Middle: function call yang error
- Top: entry point
Print Debugging
Klasik tapi efektif:
def hitung(a, b):
print(f"DEBUG: a={a}, b={b}, type={type(a)}")
return a + b
Pakai f-string {var=}:
x = 42
print(f"{x = }") # "x = 42"
Logging (Lebih Profesional)
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.debug("Detail untuk dev")
logger.info("Info biasa")
logger.warning("Warning")
logger.error("Error")
logger.critical("Critical")
# Output dengan timestamp
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s'
)
Debugger Built-in (pdb)
import pdb
def buggy():
x = 10
y = 20
pdb.set_trace() # akan pause di sini
return x + y
Saat program berhenti, ketik:
nnext linesstep into functionccontinuep varprint variableqquit
Modern: VS Code Debugger
Setup launch config (.vscode/launch.json):
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
Set breakpoint (klik di gutter sebelah line number → titik merah). Run dengan F5. Jauh lebih powerful dari print debugging.
Bagian 7 — Assert (Defensive Programming)
def hitung_bmi(berat, tinggi):
assert berat > 0, "Berat harus positif"
assert tinggi > 0, "Tinggi harus positif"
return berat / (tinggi ** 2)
hitung_bmi(-70, 1.7)
# AssertionError: Berat harus positif
Assert untuk invariants (sesuatu yang harus selalu benar).
Hati-hati: assert bisa di-disable dengan
python -O. Jangan pakai untuk validation kritis (security, business logic). Pakaiif + raiseuntuk itu.
Bagian 8 — Common Errors & Solutions
IndentationError
def foo():
print("hi") # ❌ tidak indent
Solusi: pakai 4 spasi konsisten.
KeyError di Dict
data = {"a": 1}
print(data["b"]) # ❌ KeyError
# Solusi: gunakan .get()
print(data.get("b", default))
IndexError di List
data = [1, 2, 3]
print(data[10]) # ❌ IndexError
# Solusi: cek dulu, atau pakai try/except
if 10 < len(data):
print(data[10])
TypeError: Concatenate
nama = "Budi"
umur = 25
print("Halo " + umur) # ❌ TypeError (str + int)
# Solusi: f-string
print(f"Halo {nama}, umur {umur}")
AttributeError: NoneType
def cari_user(id):
# ... return None kalau tidak ada
return None
user = cari_user(99)
print(user.nama) # ❌ AttributeError: NoneType has no attribute
# Solusi: cek None
if user is not None:
print(user.nama)
ImportError
import some_lib # ❌ ModuleNotFoundError
# Solusi: install
# pip install some_lib
# Atau pastikan virtual env aktif
# conda activate ai-prep
ValueError: Convert
int("abc") # ❌ ValueError
int("3.14") # ❌ ValueError (str → int harus integer murni)
# Solusi
try:
n = int(value)
except ValueError:
n = 0 # default
Encoding Error
open("data.txt").read() # ❌ UnicodeDecodeError di Windows kadang
# Solusi: spesifikasi encoding
open("data.txt", encoding="utf-8").read()
Bagian 9 — Defensive Programming Patterns
Validate Early, Fail Fast
def process_user(data: dict):
# Validasi di awal
if not isinstance(data, dict):
raise TypeError(f"Expected dict, got {type(data)}")
required = {"nama", "email"}
missing = required - data.keys()
if missing:
raise ValueError(f"Missing keys: {missing}")
# Logic utama
# ...
Type Hints + isinstance
def hitung(a: int, b: int) -> int:
if not isinstance(a, int):
raise TypeError(f"a harus int, got {type(a)}")
return a + b
Default Pattern
# Hindari nested if untuk default
def get_name(user, default="Anonymous"):
if user is None:
return default
if not user.get("nama"):
return default
return user["nama"]
# Lebih ringkas
def get_name(user, default="Anonymous"):
return (user or {}).get("nama") or default
Context Manager Sendiri
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.time() - self.start
print(f"Elapsed: {elapsed:.2f}s")
with Timer():
# ... slow code
pass
Common Mistakes & FAQ
❌ Mistake 1: Bare except:
# ❌ tangkap semua, termasuk KeyboardInterrupt!
try:
do_something()
except:
pass # silently swallow
# ✅ minimal pakai Exception, dan log
try:
do_something()
except Exception as e:
logger.error(f"Failed: {e}")
raise
❌ Mistake 2: Pesan error tidak informatif
# ❌
raise ValueError("Error")
# ✅
raise ValueError(f"Berat harus positif, got {berat}")
❌ Mistake 3: Pakai assert untuk validasi production
# ❌ assert bisa di-disable dengan python -O
def transfer(amount):
assert amount > 0 # bisa skip!
# ✅ pakai if + raise untuk kondisi penting
def transfer(amount):
if amount <= 0:
raise ValueError("Jumlah harus positif")
❌ Mistake 4: Catch terlalu luas
# ❌ menyembunyikan TypeError yang sebenarnya bug code
try:
user_data = fetch_user(id)
name = user_data.name
except Exception:
name = "Unknown"
# ✅ catch yang spesifik
try:
user_data = fetch_user(id)
name = user_data.name
except UserNotFoundError:
name = "Unknown"
❌ Mistake 5: Re-raise tanpa context
try:
process_data(d)
except KeyError as e:
raise RuntimeError("Failed") # ❌ context hilang
# ✅ pakai from e
try:
process_data(d)
except KeyError as e:
raise RuntimeError(f"Missing key while processing: {e}") from e
❌ Mistake 6: try block terlalu besar
# ❌ tidak jelas baris mana yang error
try:
data = load_csv(path)
cleaned = clean_data(data)
model = train(cleaned)
save(model)
except Exception as e:
print(e)
# ✅ sempit, exception spesifik
try:
data = load_csv(path)
except FileNotFoundError:
print(f"File tidak ada: {path}")
return
# proses lain di luar try kalau aman
FAQ
Q: Kapan pakai if-check (LBYL) vs try-except (EAFP)? A:
- LBYL = kalau cek murah dan harus dilakukan (e.g. cek argument valid)
- EAFP = kalau race condition mungkin (e.g. file mungkin terhapus antara cek dan akses), atau cek mahal
Python lebih suka EAFP, tapi dict.get(k, default) adalah LBYL Pythonic.
Q: Apakah exception lambat? A: Raise/catch ada overhead, tapi tidak signifikan untuk kasus biasa. Jangan pakai exception sebagai control flow biasa (jangan bikin loop bergantung exception).
Q: Bagaimana cara test exception? A: Pakai pytest:
import pytest
def test_negative_balance():
with pytest.raises(ValueError, match="positif"):
hitung_bmi(-70, 1.7)
Q: Logging vs print untuk debug? A: Untuk script kecil, print boleh. Untuk production: logging — bisa diatur level (DEBUG/INFO/WARNING), output ke file, format dengan timestamp, disable di production tanpa edit kode.
Q: Bagaimana baca traceback yang panjang (deep stack)? A:
- Mulai dari bawah (error message)
- Naik ke frame milik kamu (file kamu, bukan stdlib)
- Cek apa state variable di titik error
Q: Bagaimana set breakpoint di Python tanpa IDE? A:
breakpoint() # Python 3.7+
Akan masuk ke pdb mode. Atau pakai VS Code debugger (lebih recommend).
Cek Pemahaman
- Bisa pakai try/except untuk handle error?
- Tahu kapan pakai except spesifik vs catch-all?
- Bisa raise exception sendiri?
- Tahu beda
assertdanraise? - Bisa baca traceback dari bawah ke atas?
- Tahu beda EAFP dan LBYL?
- Bisa setup logging?
Challenge 2.8
Challenge 1 — Safe Calculator
Modifikasi calculator dari challenge sebelumnya:
- Handle ValueError saat input bukan angka
- Handle ZeroDivisionError
- Loop terus sampai user ketik exit
- Log semua error ke file
errors.log
Challenge 2 — Custom Exception
Bikin sistem perpustakaan:
BookNotFoundError,MemberNotFoundError,BookAlreadyBorrowedError- Method pinjam/kembali yang raise exception sesuai
Challenge 3 — File Reader yang Robust
Function safe_read_json(path):
- Return dict kalau sukses
- Return None kalau file tidak ada
- Print warning kalau JSON malformed
- Handle encoding error
Challenge 4 — Validator
Bikin validate_user(data: dict):
- Wajib: nama (string ≥ 2 huruf), email (regex valid)
- Optional: umur (int positif)
- Raise ValueError dengan pesan jelas
Test dengan beberapa input invalid.
Challenge 5 — Retry Logic
Function retry(func, max_attempts=3):
- Coba panggil func
- Kalau error, retry sampai max_attempts
- Kalau masih gagal, raise
def random_fail():
import random
if random.random() < 0.7:
raise RuntimeError("Random failure")
return "Success"
result = retry(random_fail, max_attempts=5)
Challenge 6 — Logging Decorator (Sneak Peek Dari Fase 9)
Bikin decorator @log_calls yang log setiap kali function dipanggil:
- Print: nama function, args, kwargs, hasil
- Log error kalau exception
@log_calls
def add(a, b):
return a + b
add(2, 3)
# LOG: add called with (2, 3) = 5
(Kalau bingung, lewati dulu, kerjain di file 09.)
Challenge 7 — Debug Yourself
Run kode ini, baca traceback, dan fix:
def process(data):
return data['nama'].upper()
users = [
{'nama': 'Budi'},
{'name': 'Ani'}, # bug 1
{'nama': None}, # bug 2
'Cici', # bug 3
]
for u in users:
print(process(u))
Tulis di jurnal: bug apa, traceback bilang apa, fix-nya gimana.
Selanjutnya: 09-advanced.md — decorator, generator, context manager, type hints lanjutan. Yang membuat code kamu jadi level pro.