Bir program “Execution complete” yazdığında genellikle ona inanırız. Hata mesajı yoksa her şeyin yolunda gittiğini düşünürüz. GitHub deposundaki projelerin README dosyalarını yeniden yazarken her betiği gerçekten çalıştırdık. Beş projede, kodun hiçbir şeyi kontrol etmeden başarı ya da makul görünen bir durum bildirdiğini gördük. Bu kodları bir yapay zekâ yazmıştı. Bu yazıda beş örneği gerçek çıktılarıyla, her birinin neden yanıltıcı olduğunu ve düzeltilmiş hallerini anlatıyoruz.
Neden Bu Konu?
Yapay zekâ araçları artık çalışan kodu saniyeler içinde üretiyor. Üretilen kod çoğunlukla düzgün görünüyor: açıklayıcı yorumlar, düzenli fonksiyonlar, renkli tablolar, kendinden emin mesajlar. Sorun da tam burada başlıyor. Bir mesajın kendinden emin olması, arkasında bir kontrol olduğu anlamına gelmiyor.
Bu yazıdaki hataların hiçbiri programı çökertmiyor. Tam tersine, hepsi düzgün biten, temiz çıktı veren programlar. Bu yüzden kodu okuyarak ya da bir kez çalıştırarak fark edilmeleri zor. Claude ile Otomasyon #3 yazısında README bakımını anlatırken bu bulgulardan birine kısaca değinmiştik. Burada hepsini bir arada ele alıyoruz, çünkü aynı alışkanlığın farklı yüzleri.
Proje bilgisi Yazarlar: Ahmet Aksoy & Claude · Tarih: 15.10.2026 Ortamlar: Python 3.12.3 (Ubuntu 24.04, yerel ayar
tr_TR.UTF-8) ve Docker’dapython:3.12-slim(3.12.15), gerektiğinde ağ bağlantısı kapalı (--network none) İncelenen projeler: 26, 29, 34, 37, 39 · Örnek kod: proje 45
1. Hata Mesajı Basıp Başarıyla Çıkmak
37 numaralı proje, bir klasördeki dosyaları uzantılarına göre alt klasörlere taşıyan küçük bir komut satırı aracı. Kaynak klasör yoksa şunu yapıyor:
if not src_path.exists() or not src_path.is_dir():
print(f"[Error] Source directory '{source_dir}' does not exist or is not a directory.")
return
Ekranda bir hata mesajı görünüyor, bu doğru. Ama fonksiyon yalnızca return ile dönüyor ve program normal biçimde sona eriyor. Çalıştırdığımızda:
$ python3 main.py ./yok ./hedef
[Error] Source directory './yok' does not exist or is not a directory.
$ echo $?
0
Komut satırında her programın bir çıkış kodu vardır. 0 “başarılı”, 0’dan farklı her değer “başarısız” anlamına gelir. Ekrandaki mesajı insan okur, çıkış kodunu ise kabuk betikleri, zamanlanmış görevler (cron) ve CI sistemleri okur. Bu araç bir yedekleme betiğinin içinde şöyle kullanılsaydı:
python3 main.py ./indirilenler ./arsiv && rm -rf ./indirilenler_yedek
&& işareti “ilk komut başarılıysa ikincisini çalıştır” demek. Kaynak klasör yokken de çıkış kodu 0 olduğu için zincir devam ediyor ve yedek siliniyor. Bunu geçici bir deneme klasöründe denedik, yedek klasörü gerçekten silindi.
39 numaralı web kazıma projesinde aynı durum üç farklı biçimde karşımıza çıktı. Normal çalışma, 404 dönen bir sayfa ve çözülemeyen bir alan adı için çıkış kodu her seferinde 0:
[Error] Failed to connect. HTTP Status Code: 404
çıkış kodu: 0
[Critical Network Error] Connection timed out or failed: ... Failed to resolve 'nonexistent.invalid' ...
çıkış kodu: 0
Bu çıktıda ikinci bir sorun daha var: 404 durumunda program “Failed to connect” diyor. Oysa bağlantı kurulmuş, sunucu yanıt vermiş, yalnızca sayfa yok.
Düzeltme tek satır: hata durumunda sys.exit() kullanmak. sys.exit() fonksiyonuna bir metin verirseniz Python bu metni hata akışına (stderr) yazar ve 1 koduyla çıkar:
if not Path(sys.argv[1]).is_dir():
sys.exit(f"[Error] source directory does not exist: {sys.argv[1]}")
2. “Execution complete” Derken Dosya Ezmek
Aynı projede daha tehlikeli bir sorun var. Hedef klasörde aynı adlı bir dosya varsa ne oluyor? Denedik: hedef/Documents/rapor.txt içine “2025 vergi beyannamesi – ESKİ” yazdık, kaynak klasöre de aynı adlı yeni bir rapor.txt koyduk:
-> Mapping: rapor.txt -> [Documents] folder
--------------------------------------------------
Execution complete. Total items logged/processed: 1
çıkış kodu: 0
--- hedef/Documents/rapor.txt:
yeni rapor
Eski dosya gitti. Program uyarmadı, sormadı ve işin “tamamlandığını” bildirdi. Bunun nedeni, kodun kullandığı shutil.move() fonksiyonunun Linux’ta var olan hedef dosyanın üzerine sessizce yazması. Bu fonksiyonun belgelenmiş davranışı, yani hata değil. Hata, kodun bu durumu hiç düşünmemiş olması.
Daha da ilginci, aracın bir --dry-run (deneme) seçeneği var. Bu seçenek ekrana yalnızca “rapor.txt → Documents” yazıyor, hedefte aynı adlı bir dosya olduğunu söylemiyor. Yani deneme modu da sizi uyarmıyor.
Düzeltme: Taşımadan önce hedefi kontrol etmek ve dosya varsa reddetmek (ya da adı değiştirmek):
def move_checks(src: Path, dst: Path) -> None:
if dst.exists():
raise FileExistsError(f"refusing to overwrite {dst}")
shutil.move(src, dst)
3. Her Hatayı “404” Yapmak
26 numaralı proje, thread’ler (iş parçacıkları) ile süreçleri (multiprocessing) karşılaştıran bir ölçüm betiği. Ağ isteği yapan fonksiyonu şöyle:
def io_bound_task(url: str) -> int:
try:
with urllib.request.urlopen(url, timeout=5) as response:
return response.status
except Exception as e:
return 404
except Exception her türlü hatayı yakalıyor: alan adının çözülememesini, zaman aşımını, sertifika hatasını, ağ kablosunun takılı olmamasını. Hepsi “404” oluyor. 404 ise “sunucuya ulaştım, sayfa yok” demek. Yani fonksiyon gerçekte olmayan bir sunucu yanıtı uyduruyor.
Betik bu sonuçları hiç yazdırmadığı için durum daha da gizli kalıyor. Betiği Docker’da ağ bağlantısı tamamen kapalıyken çalıştırdık:
STAGE 2: I/O-Bound Task (Network Requests)
==================================================
Threading (I/O-Bound) Duration : 0.0511 seconds
Multiprocessing (I/O-Bound) Duration : 0.0239 seconds
çıkış kodu: 0
Tek bir ağ isteği bile yapılmadı, ama betik iki süre ölçtü ve başarıyla bitti. Asıl şaşırtıcı olan şu: aynı fonksiyonla ağsız ölçümü ilk denediğimizde sonuç tersineydi, thread’ler daha hızlı çıktı (0.0145 ve 0.1289 saniye). Yani betik, hiçbir şey ölçmediği bir durumda bile bazen beklenen sonucu “doğruladı”. Okur bu sayılara bakıp “thread’ler ağ işlerinde daha hızlı” sonucunu çıkarabilirdi.
Düzeltme: Sunucunun yanıt verdiği durumla hiç yanıt gelmediği durumu ayırmak. urllib bunu kendisi yapıyor: HTTPError sunucudan gelen bir hata kodunu, URLError ise yanıtın hiç gelmediğini gösterir.
def status_checks(url: str) -> int | str:
try:
with urllib.request.urlopen(url, timeout=5) as resp:
return resp.status
except urllib.error.HTTPError as exc: # sunucu yanıt verdi
return exc.code
except (urllib.error.URLError, TimeoutError) as exc: # yanıt hiç gelmedi
return f"no response: {getattr(exc, 'reason', exc)}"
Ölçüm yapan bir betikte, ölçülen işin gerçekten yapıldığını da kontrol etmek gerekir. En basit yol, sonuçları bir kez ekrana yazdırmaktır.
4. Koda Gömülü Durum Metni
34 numaralı proje Poetry ile paket yönetimini anlatıyor. Çalıştırıldığında ortam bilgilerini düzenli bir tabloyla gösteriyor. Tablonun bir satırı şöyle üretiliyor:
table.add_row("Rich Library Status", "Successfully Resolved via Poetry")
Bu bir kontrol değil, sabit bir metin. Program nasıl çalıştırılırsa çalıştırılsın aynı şeyi yazıyor. Denemek için Poetry’nin hiç kurulu olmadığı bir Docker konteynerinde rich paketini düz pip ile kurup betiği çalıştırdık:
poetry var mı: YOK
┃ Parameter ┃ Value ┃
│ Python Executable Path │ /usr/local/bin/python │
│ Python Version │ 3.12.15 │
│ Rich Library Status │ Successfully Resolved via Poetry │
Tablodaki ilk iki satır gerçek bilgi (sys.executable ve sys.version), üçüncüsü değil. Aynı tabloda yan yana durdukları için hepsi aynı güvenilirlikte görünüyor. 40 numaralı projede de benzer bir durum vardı: sanal ortam algılanınca adı ne olursa olsun ekrana “ACTIVE (.venv)” yazılıyor.
Düzeltme: Durumu yazmak yerine okumak:
try:
version = importlib.metadata.version("rich")
except importlib.metadata.PackageNotFoundError:
version = "NOT INSTALLED"
in_venv = sys.prefix != sys.base_prefix
5. Bakmadan “rolled back” Demek
Beşinci örneği 19 Ekim yazımızda ayrıntısıyla anlatmıştık. 29 numaralı projedeki veritabanı yöneticisi bir hata olduğunda işlemi geri alıyor ve bunu ekrana yazıyor:
except Exception as e:
conn.rollback()
print(f"[Database Error] Transaction aborted & rolled back: {e}")
Türkçe yerel ayarlı bilgisayarımızda Python 3.12 ile aynı blokta iki kayıt eklemeye çalıştık. İkinci kayıt benzersizlik kuralını bozduğu için hata verdi:
[Database Error] Transaction aborted & rolled back: UNIQUE constraint failed: inventory_items.sku
tablodaki satır sayısı: 1
Ekranda “rolled back” yazıyor ama ilk kayıt tabloda duruyor. Nedeni, Python 3.11 ve 3.12’nin Türkçe yerel ayarda INSERT komutunu tanımaması ve işlemi hiç başlatmaması. conn.rollback() çağrılıyor, ama geri alınacak bir işlem yok. Buradaki ders 19 Ekim yazısındakinden biraz farklı: mesaj, rollback() fonksiyonunun çağrıldığını söylüyor, verinin geri alındığını değil. İkisi aynı şey değil.
Düzeltme iki katmanlı. Asıl sorunu autocommit=False (Python 3.12+) çözüyor. Mesajın doğru olması için ise hatadan sonra tabloya bakmak gerekiyor:
except sqlite3.IntegrityError:
left = conn.execute("SELECT count(*) FROM items").fetchone()[0]
return "rolled back" if left == 0 else f"NOT rolled back, {left} row(s) left"
Beşi Bir Arada: Söyleyen ve Kontrol Eden Kod
Beş örneği tek bir betikte topladık (proje 45). Her bölüm önce yalnızca “söyleyen” sürümü, sonra “kontrol eden” sürümü çalıştırıyor. Betik internet bağlantısı ve üçüncü taraf paket gerektirmiyor. Kendi bilgisayarımızdaki çıktısı:
== 1. Exit code ==
says : exit code 0 -> 'cmd && next' continues
checks: exit code 1 -> 'cmd && next' stops
== 2. Overwrite ==
says : moved report.txt now: 'new report'
checks: refused (FileExistsError) report.txt now: 'OLD report, only copy'
== 3. Error -> 404 ==
says : 404
checks: no response: [Errno -2] Name or service not known
== 4. Hard-coded status ==
says : Rich Library Status -> Successfully Resolved via Poetry
checks: rich 13.7.1, virtualenv: False, POETRY_ACTIVE=-
== 5. 'rolled back' ==
Python 3.12.3, LC_CTYPE=tr_TR.UTF-8
says : rolled back
checks : NOT rolled back, 1 row(s) left
checks + fixed: rolled back
- bölümdeki üç satır dersin özeti: ilk satır sorunu gizliyor, ikincisi sorunu çözmüyor ama en azından doğruyu söylüyor, üçüncüsü hem çözüyor hem doğruluyor.
Yapay Zekânın Yazdığı Kodu Değerlendirirken Ne Arıyoruz?
Beş örneğin ortak noktası, kodun yaptığı iş ile söylediği şey arasındaki bağın kopuk olması. Bu bulgulardan sonra yapay zekânın yazdığı kodu okurken kendimize şu soruları soruyoruz:
- Program hata durumunda nasıl bitiyor? Hata mesajından sonra
returnmı var,sys.exit(1)mi? Bunu anlamanın en hızlı yolu, programı bilerek yanlış bir girdiyle çalıştırıpecho $?yazmak. except Exceptionne döndürüyor? Bütün hataları yakalayıp makul görünen bir değer (404, 0, boş liste) döndüren bir blok gördüğünüzde durun. O değer gerçekte olmamış bir şeyi anlatıyor olabilir.- Ekrandaki durum metni nereden geliyor? “Successfully”, “ACTIVE”, “RESOLVED” gibi kelimelerin hesaplanan bir değerden mi geldiğine, yoksa tırnak içinde mi yazıldığına bakın.
- Mesaj eylemi mi, sonucu mu anlatıyor? “rolled back” ve “Execution complete” bir fonksiyonun çağrıldığını söylüyor. Sonuç önemliyse, sonuca bakan bir satır gerekir.
- Kötü durumu denediniz mi? Bu beş hatanın hiçbiri “her şey yolunda” senaryosunda görünmüyor. Biz onları olmayan bir klasör, aynı adlı bir dosya, kapalı bir ağ, eksik bir araç ve bozulan bir kuralla denediğimizde gördük.
Bu hataların yapay zekâya özgü olmadığını da belirtmek gerekiyor. İnsanlar da böyle kod yazıyor, yapay zekâ bu kodlardan öğrendi. Fark hızda: yapay zekâ bu kodu saniyeler içinde, düzgün yorumlarla ve kendinden emin mesajlarla üretiyor. Kodu yazmak hızlandıkça, onu doğru durumlarda denemek daha önemli hale geliyor.
Kodu Kendiniz Deneyin
Beş örneğin tamamı proje 45’te tek bir main.py dosyasında. Betik Python 3.12 veya daha yenisini gerektiriyor, geçici dosyalarını kendisi silip temizliyor:
python3 main.py
Beşinci bölümdeki “NOT rolled back” satırını görmek için bilgisayarınızda Türkçe yerel ayarın (tr_TR.UTF-8) kurulu olması ve Python 3.11 ya da 3.12 kullanmanız gerekiyor. Başka ortamlarda üç satır da “rolled back” der ve betik bunu bir notla belirtir. Özgün kodları görmek isterseniz depodaki 26, 29, 34, 37 ve 39 numaralı projelere bakabilirsiniz. Bu projelerin README dosyalarına sınırlamalarını not olarak ekledik.
Kaynaklar
- sys.exit — Python belgeleri
- urllib.error — Python belgeleri:
HTTPErrorveURLErrorfarkı - shutil.move — Python belgeleri: var olan hedef dosyada davranış
- sqlite3: işlem kontrolü ve gh-108767
- Doğru Görünen Kod, Yanlış Sonuç: Python’da with ve sqlite3 Tuzağı
- Claude ile Otomasyon #3: 19 Projenin README Dosyasını Kodu Çalıştırarak Yeniden Yazdık
Ahmet Aksoy & Claude
Not: Bu yazıda incelediğimiz kodu ve benzer projelerin kaynak kodlarını https://github.com/ahmetax/practical-python-examples adresinde bulabilirsiniz.



