Neden önemli?
Anadolu Market'in gece çalışan ciro raporu bu sabah İzmir için 0 TL gösterdi. Program hata vermedi, sadece yanlış sonuç üretti. Ekipte biri "dün çalışıyordu" dedi, bir başkası kodu baştan yazmayı önerdi. Deneyimli geliştirici ise şunu yaptı: hatayı tekrar üretti, veriyi küçülttü, iki print ekledi ve on dakikada sorunun bir sözlük anahtarındaki tek harf olduğunu buldu. Hata ayıklama (debugging), yazılımcının günlük işinin belki de yarısıdır ve şansa değil yönteme dayanır. Bu derste o yöntemi öğreneceksin.
try/except ile hatayı yakalamayı ve kendi hatanı fırlatmayı "Hatalar ve istisnalar" dersinde gördün. Burada amaç farklı: hatanın nedenini bulmak ve düzeltmek.
Üç hata ailesi
| Aile | Ne zaman ortaya çıkar? | Örnek | Belirti |
|---|---|---|---|
| Sözdizimi hatası (syntax error) | Kod daha çalışmadan, derlenirken | Kapanmamış parantez, eksik iki nokta, yanlış girinti | Program hiç başlamaz; SyntaxError, IndentationError |
| Çalışma zamanı hatası (runtime error, exception) | Kod çalışırken, o satıra gelince | Sıfıra bölme, olmayan anahtar, yanlış tür | Traceback ile durur: ZeroDivisionError, KeyError, TypeError |
| Mantık hatası (logic error) | Hiçbir zaman "patlamaz" | Yanlış aralık, yanlış koşul, yanlış formül | Program sessizce yanlış sonuç verir |
En tehlikelisi sonuncusudur: kimse fark etmezse yanlış rakam yönetim raporuna kadar gider. Sözdizimi ve çalışma zamanı hataları ise aslında dosttur; sana tam olarak nerede durduklarını söylerler.
Traceback'i alttan yukarı oku
Python bir istisna fırlattığında geriye dönük iz (traceback) yazdırır. Yeni başlayanlar en üstten okumaya başlar ve kaybolur. Doğru sıra tam tersidir:
Traceback (most recent call last):
File "gunluk_rapor.py", line 12, in <module>
print(sehir_ozeti(siparisler))
File "gunluk_rapor.py", line 7, in sehir_ozeti
toplam[s["sehir"]] += s["tutar"]
KeyError: 'İzmir'
- En alt satır: hata türü ve mesajı.
KeyError: 'İzmir'→ sözlükte "İzmir" anahtarı yok. - Hemen üstündeki File satırı: hatanın gerçekleştiği yer. 7. satır,
sehir_ozetifonksiyonu. - Kod satırı:
toplam[s["sehir"]] += s["tutar"].+=önce okur; anahtar ilk kez geliyorsa okuma başarısız olur. - Yukarı çık: bu fonksiyonu kim çağırdı? 12. satır. Üst satırlar "buraya nasıl gelindi" hikâyesidir.
"most recent call last" ifadesi zaten ipucunu verir: en son çağrı en alttadır. Kütüphane kodunun içinde biten bir traceback'te, alttan yukarı çıkarken kendi dosyanın ilk satırını ara; hata çoğunlukla oraya senin verdiğin bir değerden kaynaklanır.
siparisler = [
{"sehir": "İzmir", "tutar": 300},
{"sehir": "Ankara", "tutar": 150},
{"sehir": "İzmir", "tutar": 120},
]
def sehir_ozeti(siparisler):
toplam = {}
for s in siparisler:
# Hatalı hâli: toplam[s["sehir"]] += s["tutar"]
toplam[s["sehir"]] = toplam.get(s["sehir"], 0) + s["tutar"]
return toplam
print(sehir_ozeti(siparisler))
Hata türüne göre ilk bakılacak yer
| Hata | Genelde ne demek? | İlk bakacağın yer |
|---|---|---|
SyntaxError | Python cümleyi anlayamadı | Gösterilen satır ve bir üstü: kapanmamış parantez/tırnak, eksik : |
IndentationError | Girinti tutarsız | Sekme/boşluk karışımı, blok sonrası boş gövde |
NameError | Bu ad tanımlı değil | Yazım hatası, tanımdan önce kullanım, yanlış kapsam |
TypeError | Bu türle bu işlem olmaz | input()'tan gelen metin, None dönen fonksiyon, eksik argüman |
ValueError | Tür doğru, değer uygunsuz | int("12,5") gibi dönüşümler, veri temizliği |
KeyError | Sözlükte bu anahtar yok | Anahtarın yazımı, büyük-küçük harf, Türkçe karakter |
IndexError | Liste bu kadar uzun değil | range sınırları, boş liste, off-by-one |
AttributeError | Bu nesnede bu nitelik yok | Değişken beklediğin türde değil (çoğu zaman None) |
ZeroDivisionError | Sıfıra bölme | Boş listede ortalama, sıfır adet |
print ile izleme ve f"{x=}" kalıbı
En eski ve hâlâ en hızlı araç: şüphelendiğin yere değerleri yazdırmak. Python 3.8'den beri f-string içinde = kullanırsan hem ifadeyi hem değerini yazar; etiketi elle yazma derdi biter.
def kargo_ucreti(agirlik_kg, sehir):
taban = 40
ek = (agirlik_kg - 1) * 12 if agirlik_kg > 1 else 0
uzak = sehir in ("Van", "Hakkari")
print(f"{agirlik_kg=} {ek=} {uzak=}") # geçici izleme satırı
ucret = taban + ek + (25 if uzak else 0)
print(f"{ucret=} {ucret / agirlik_kg=:.2f}")
return ucret
kargo_ucreti(3, "Van")
kargo_ucreti(0.5, "İzmir")
print satırlarının sonuna # DEBUG yaz; iş bitince hepsini tek aramayla bul ve sil. Kalıcı izleme gerekiyorsa print değil logging kullanılır (orta seviyede göreceksin).assert ile varsayım kontrolü
Her kodun içinde söylenmemiş varsayımlar vardır: "liste boş olmaz", "indirim oranı 0 ile 1 arasındadır". assert koşul, "mesaj" bu varsayımı yazıya döker; koşul yanlışsa AssertionError fırlatır. Böylece hata, yanlış değerin doğduğu yerde yakalanır, üç fonksiyon sonra değil.
def indirim_uygula(fiyat, oran):
assert 0 <= oran <= 1, f"oran 0-1 arasında olmalı, gelen: {oran}"
yeni = fiyat * (1 - oran)
assert yeni <= fiyat, "indirimli fiyat eskisinden büyük olamaz"
return round(yeni, 2)
print(indirim_uygula(200, 0.15))
try:
print(indirim_uygula(200, 15)) # yüzde yerine 15 verildi
except AssertionError as hata:
print("Yakalandı:", hata)
-O seçeneğiyle çalıştırılınca tüm assert satırları atlanır. Kullanıcıdan ya da dosyadan gelen veriyi doğrulamak için if + raise ValueError kullan; assert yalnızca "bu asla olmamalı" dediğin programcı varsayımları içindir.breakpoint() ve pdb
print'in yetmediği yerde hata ayıklayıcı (debugger) devreye girer: programı bir satırda durdurur, değişkenlere bakmanı ve satır satır ilerlemeni sağlar. Kodun içine breakpoint() yazman yeterli; Python yerleşik pdb ayıklayıcısını açar.
def faiz_hesapla(anapara, yillik_oran, ay):
aylik = yillik_oran / 12
breakpoint() # program burada durur, (Pdb) istemi açılır
return anapara * (1 + aylik) ** ay
faiz_hesapla(10_000, 0.42, 12)
| Komut | Açılımı | Ne yapar? |
|---|---|---|
n | next | Sonraki satıra geç; fonksiyon çağrısının içine girmez |
s | step | Satırda bir fonksiyon çağrısı varsa içine gir |
c | continue | Bir sonraki durma noktasına kadar çalış |
p ifade | Değeri göster: p aylik, p anapara * 2 | |
l | list | Bulunduğun yerin çevresindeki kodu göster |
w | where | Çağrı yığınını göster (canlı traceback) |
q | quit | Ayıklayıcıdan ve programdan çık |
breakpoint()'i burada değil, terminalde dene: python faiz.py. Program çökmüşse python -m pdb faiz.py ile başlatıp hata anında otomatik durdurabilirsin (post-mortem).VS Code hata ayıklayıcısı
Aynı işi fareyle yapmak istersen VS Code'un Python eklentisi yeterli:
- Satır numarasının soluna tıkla (ya da F9): kırmızı nokta, yani kesme noktası (breakpoint).
- F5 → "Python File". Program kırmızı noktada durur.
- Sol panelde Variables tüm yerel değişkenleri gösterir; Watch'a ifade ekleyip izleyebilirsin.
- F10 step over (n), F11 step into (s), Shift+F11 fonksiyondan çık, F5 devam (c).
- Kesme noktasına sağ tıklayıp koşullu yapabilirsin:
s["sehir"] == "İzmir". Bin kayıtlık döngüde yalnızca ilgilendiğin kayıtta durur. - Debug Console'da durduğun andaki değişkenlerle Python ifadesi çalıştırabilirsin.
Sorunu küçült: minimal örnek
Elinde 50 bin satırlık sipariş dosyası ve bir yerde patlayan bir rapor var. Gözle aramak yerine sorunu üreten en küçük girdiyi bul. Önce hatayı üreten kaydı otomatik yakala, sonra yalnızca o kayıtla çalış:
kayitlar = [
{"no": "AM-1", "tutar": "120.5"},
{"no": "AM-2", "tutar": "89"},
{"no": "AM-3", "tutar": "1.250,00"}, # Türkçe biçim: float'a çevrilmez
{"no": "AM-4", "tutar": "40"},
]
for i, k in enumerate(kayitlar):
try:
float(k["tutar"])
except ValueError as hata:
print(f"{i=} {k['no']=} -> {hata}")
break
Artık sorun "rapor çalışmıyor" değil, "float("1.250,00") ValueError veriyor". Bu cümleyle düzeltme yapmak, aramak ve soru sormak çok kolaydır. Minimal örneğin kuralları: hatayı hâlâ üretsin, bunun dışında her şey silinsin, başkası kopyalayıp çalıştırabilsin.
Rubber duck: sesli anlat
Masaya bir lastik ördek koy ve kodunu ona satır satır anlat: "Burada listeyi geziyorum, burada toplamı artırıyorum, burada döndürüyorum..." Çoğu zaman cümlenin ortasında "dur, return döngünün içinde" dersin. Yöntemin adı komik ama işe yarar, çünkü sesli anlatmak seni varsaydığın şeyi değil, kodun gerçekten yaptığını okumaya zorlar.
Hata mesajını aramak ve yapay zekâya sormak
Hata mesajının genel kısmını ara, sana özel kısmını çıkar: "TypeError: unsupported operand type(s) for +: 'int' and 'str'" iyi bir aramadır; dosya adların ve değişken adların değildir. Yapay zekâya ya da bir foruma sorarken şu beşini ver:
- Ne yapmaya çalışıyorsun? (bir cümle iş hedefi)
- Minimal kod (çalıştırılabilir, gizli bilgi ve müşteri verisi içermeyen)
- Tam hata mesajı (traceback'in tamamı, ekran görüntüsü değil metin)
- Beklenen ve gelen sonuç
- Denediklerin ve Python sürümün
Klasik böcekler kataloğu
| Böcek | Belirti | Düzeltme |
|---|---|---|
| Off-by-one | Son ya da ilk eleman eksik | range bitişinin hariç olduğunu hatırla |
Döngü içinde return | Yalnızca ilk eleman işlenir | return'ü döngü hizasından çıkar |
== yerine = ya da tersi | Değer güncellenmez | Atama =, karşılaştırma == |
| Tamsayı bölme | Kuruşlar kaybolur | // yerine / |
| Değişebilir varsayılan | Çağrılar arasında veri sızar | None varsayılanı + içeride yeni liste |
| Gölgeleme (shadowing) | 'int' object is not callable | sum, list, max adlarını değişkene verme |
| Yanlış anahtar | KeyError ya da .get ile sessiz 0 | Anahtarı veriden kopyala |
Bilimsel yöntem olarak hata ayıklama
Gözlem → hipotez → deney → sonuç. "Sanırım tutar metin olarak geliyor" bir hipotezdir; print(f"{type(tutar)=}") onun deneyidir. Hipotezleri tek tek ele, aynı anda üç şeyi değiştirme; yoksa hangisinin düzelttiğini bilemezsin.
İkiye bölerek arama
Kod dün çalışıyordu, bugün çalışmıyorsa aradaki değişikliklerin ortasını dene, sonra kalan yarının ortasını... Git bunu git bisect komutuyla otomatik yapar: yüz commit arasından bozan commit'i yaklaşık yedi denemede bulursun. Aynı fikir veri için de geçerlidir: dosyanın yarısıyla dene, hata hangi yarıdaysa onu böl.
Heisenbug ve ortam farkları
Bazı hatalar gözlemlediğinde kaybolur (zamanlama, rastgelelik, önbellek (cache)). Rastgelelikte random.seed ile tekrarlanabilir hâle getir; ortam farkında python --version, pip freeze ve işletim sistemini karşılaştır. "Bende çalışıyor" cümlesi bir sonuç değil, bir ipucudur: iki ortam arasında bir fark var demektir.
Post-mortem
Uzun süren bir iş gecenin üçünde çöktüyse, interaktif konsolda import pdb; pdb.pm() son istisnanın yığınına girip o anki değişkenleri gösterir. Üretimde ise log ve hata izleme servisleri (Sentry gibi) traceback'i değişken değerleriyle birlikte saklar.