Çalışan Kod Yetmez: Agentic Sistemlerde Kaliteli ve Sürdürülebilir Kod Nasıl Üretilir?

Çalışan Kod Yetmez: Agentic Sistemlerde Kaliteli ve Sürdürülebilir Kod Nasıl Üretilir?

Agent kodu yazdı, testler yeşil. Peki altı ay sonra o koda dokunmak kolay olacak mı?

Merhabalar,

Bu yazıda coding agent’larla çalışırken kolay gözden kaçan bir konuyu konuşacağız: Kod çalışıyor, peki kaliteli mi? Agent’ın neden çalışan ama bakımı zor kod yazdığına, bunun rakamlara nasıl yansıdığına ve önüne pratikte nasıl geçildiğine bakacağız.

Küçük bir senaryoyla başlayalım. Bir coding agent’a mevcut uygulamadaki müşteri arama ekranına Brand filtresi eklemesini söylüyoruz. Agent çalışıyor, build başarılı, testler yeşil, pull request açılıyor. Reviewer diff’e göz atıyor, çalıştığını görüyor ve onaylıyor.

Üç ay sonra aynı koda baktığımızda şunları görüyoruz: Filtre mantığı iki ayrı yerde, birbirine benzeyen ama aynı olmayan iki kopya halinde duruyor. Bir controller, service’i atlayıp doğrudan repository’yi çağırıyor. Bir catch bloğu hata olunca sessizce boş liste döndürüyor. Projede zaten olan bir yardımcı sınıfın yerine yenisi yazılmış. Hiçbiri build’i kırmıyor, hiçbir test kırmızı değil. Ama bir sonraki geliştirici için, ya da bir sonraki agent için, hepsi küçük birer tuzak.

Bir evin elektrik tesisatı gibi düşünebiliriz. Lambalar yanıyor, her şey çalışıyor. Ama kablolar plansız çekilmiş, sigorta kutusunda etiket yok. Beş yıl sonra bir priz eklemek için duvarı yıkmak zorunda kaldığımızda “ama çalışıyordu” demek pek teselli olmuyor.

Peki bu sadece bizim hissimiz mi? Rakamlara bakalım.

Rakamlar Ne Diyor?

Elimizde birkaç çalışma var. Hepsi aynı ağırlıkta değil ama gösterdikleri yön birbirine benziyor.

CodeRabbit 470 açık kaynak pull request’i incelemiş, 320’si AI destekli, 150’si tamamen insan yazımı. AI destekli PR’larda PR başına ortalama 10,83 sorun çıkarken insan yazımlarında bu sayı 6,45. Yani yaklaşık 1,7 kat. Fark en çok okunabilirlikte (yaklaşık 3 kat), mantık hatalarında (yüzde 75) ve hata yönetiminde (yaklaşık 2 kat) görülüyor. Örneklem küçük ve raporu bir code review ürünü yayımlamış, bunu akılda tutmak lazım.

GitClear ve GitKraken’ın 2023–2026 arasındaki yaklaşık 623 milyon kod değişikliğini incelediği analize göre (LeadDev’in aktarımıyla) kod tekrarı AI öncesine kıyasla yüzde 81 artmış, mevcut kodun yeniden kullanımı yüzde 70 düşmüş, hataları yutan yapılar (catch blokları, güvenli gezinme operatörleri) yüzde 47 artmış. Yani baştaki üç tuzağın neredeyse aynısı: Tekrar, yeniden kullanmak yerine yeniden yazmak ve örtülen hatalar.

Bir akademik çalışma (arXiv, “Debt Behind the AI Boom”) GitHub’daki 6.299 repoda 302.579 AI imzalı commit’i statik analiz araçlarıyla taramış. İncelenen her AI asistanında commit’lerin yüzde 15’ten fazlası en az bir sorun getiriyor, sorunların büyük kısmı da code smell. Getirilen sorunların yüzde 22,7’si ise son sürümde hâlâ duruyor. Yani agent’ın bıraktığı borcun bir kısmını sonradan kimse ödemiyor.

Güvenlik tarafında Veracode 100’den fazla modeli 80 görevle test etmiş. Güvenli ve güvensiz yöntem arasında seçim yapabildiklerinde modeller yüzde 45 oranında güvensiz olanı seçmiş. Daha büyük modeller bu konuda belirgin şekilde daha iyi değil.

Google’ın 2025 DORA raporu da büyük resmi veriyor. Geliştiricilerin yaklaşık yüzde 90’ı AI kullanıyor ve AI artık teslim hızıyla pozitif ilişkili. Ama başarısız değişiklikler ve yeniden iş gibi instability göstergeleriyle de ilişkili olmaya devam ediyor. Raporun deyimiyle AI bir “ayna ve çarpan”: İyi çalışan sistemi daha iyi, dağınık sistemi daha dağınık hale getiriyor.

Bir de hissiyat meselesi var. METR’in 16 deneyimli açık kaynak geliştiriciyle yaptığı çalışmada AI kullananlar görevleri yüzde 19 daha geç bitirmiş, ama kendileri yüzde 20 hızlandıklarını düşünüyormuş. Çalışma erken 2025 araçlarıyla ve küçük bir grupla yapıldı, yazarları da genellenmemesi gerektiğini söylüyor. Yine de “iyi gidiyor gibi hissediyorum” ile “iyi gidiyor” aynı şey değil. Kod kalitesi için de bu geçerli.

Agent Neden Böyle Yapıyor?

Bence üç sebebi var.

Birincisi, agent “bitmiş gibi görünene” kadar çalışıyor. Ona çalıştırabileceği bir kontrol (test, build) verirsek o kontrol geçene kadar gidiyor, ötesine gitmiyor. Anthropic de Claude Code rehberinde aynı şeyi söylüyor: Agent iş bitmiş göründüğünde duruyor. “Testler geçsin” dediğimizde isimlendirme, tekrar, katman ihlali, hata yutma gibi testlerin ölçmediği her şey agent için serbest bölgeye dönüşüyor.

İkincisi, projenin tamamını görmüyor. Repository’nin hepsi context’inde durmuyor, ihtiyaç oldukça arıyor. Aramayı akıl etmezse ya da yardımcı sınıfın var olduğunu bilmezse yenisini yazıyor. Projeye yeni giren bir geliştirici de aynısını yapabilir.

Üçüncüsü, ekibin yazılı olmayan kuralları onun masasında yok. “Controller repository’ye doğrudan erişmez” cümlesi ekibin kafasında duruyor. Yazılı değilse agent’ın bunu bilmesinin bir yolu yok.

Üçünün çözümü de aynı yönde: Kaliteyi agent’ın iyi niyetine bırakmak yerine çalıştığı ortama yazmak. OpenAI’ın harness engineering yazısı da bu yönde.

Kaliteyi Ortama Yazmak

OpenAI’ın anlattığı deneyde 3 kişiyle başlayıp 7 kişiye çıkan bir ekip, beş ayda yaklaşık 1 milyon satır kod ve 1.500 civarı pull request üretmiş. Rakamlardan çok ekibin işi nasıl tarif ettiği ilginç: Çalışma, kod yazmaktan çok ortamı tasarlamaya, niyeti anlatmaya ve geri bildirim döngüleri kurmaya kaymış.

Üç şey öne çıkıyor. Mimari katmanlar (Types → Config → Repo → Service → Runtime → UI) özel linter’larla otomatik olarak zorlanmış ve linter mesajları agent’a düzeltmeyi anlatacak şekilde yazılmış. AGENTS.md yaklaşık 100 satırlık bir içindekiler tablosu olarak kalmış, ayrıntılar bir docs/ klasörüne taşınmış. Düzenli çalışan “çöp toplama” görevleri kodun kurallardan sapmasını tarayıp küçük refactoring PR’ları açmış. Yazıdaki ilke de şu: Değişmezleri zorla, implementasyonu mikro yönetme.

Bunu kendi projemize nasıl taşırız? Altı adımda bakalım.

1. Kaliteyi Tarif Etmek: Kısa Bir Kural Dosyası

“Temiz kod yaz.” cümlesi agent için bir şey ifade etmiyor. Anthropic CLAUDE.md için kendiliğinden belli şeyleri yazmamayı, her satır için de “bunu silersem agent hata yapar mı?” diye sormayı öneriyor. Dosya şişerse önemli kurallar gürültüde kayboluyor. Asıl değerli olan, koddan çıkarılamayan bilgiler: Çalıştırılacak komutlar, mimari kararlar, projeye özgü tuzaklar. Bizim senaryo için şöyle bir dosya yeterli olurdu:

# Mimari
- Controller repository'ye doğrudan erişmez, Service üzerinden gider.
- Filtreleme mantığı CustomerFilterBuilder'da toplanır, başka yerde kopyalanmaz.

# Doğrulama
- İş bittiğinde: dotnet build, dotnet test, dotnet format --verify-no-changes

# Dikkat
- catch bloğu hatayı ya loglayıp yeniden fırlatır ya da çağırana anlamlı bir
  sonuç döner. Sessizce yutmaz.

Ama bu dosya bir tavsiye. Agent uyabilir, unutabilir, dosya uzadıkça kaçırabilir. Kuralları bir de makineye yazmak gerekiyor, 3. adımda ona geleceğiz.

2. Önce Keşfet, Sonra Planla, Sonra Yaz

Anthropic’in önerdiği akış dört adım: Keşfet, planla, uygula, commit et. Plan modunda agent dosyaları okuyor ama hiçbir şeyi değiştirmiyor. Tekrar eden kod problemine en iyi çare burada: Plana “benzer işi yapan bir kod var mı?” sorusunu koymak. Prompt’ta mevcut bir örneği göstermek de çok işe yarıyor. Anthropic’in örneğinde yeni bir widget isteniyor ama agent’a “ana sayfadaki mevcut widget’lara bak, aynı kalıbı izle, projede olmayan kütüphane ekleme” deniyor.

Hata düzeltmede de mantık aynı: Önce hatayı yeniden üreten başarısız bir test, sonra düzeltme. Böylece “düzeldi” demek ölçülebilir oluyor. Dengeleyici bir not da yine Anthropic’ten: Diff’i tek cümleyle anlatabiliyorsak plana gerek yok. Typo düzeltmek için plan modu açmak sadece yavaşlatır.

3. Kuralları Prompt’a Değil Araca Yazmak

Anthropic’in ifadesiyle CLAUDE.md talimatları tavsiye niteliğinde, hook’lar ise deterministik. Her dosya düzenlemesinden sonra formatter ve linter çalıştıran bir hook, ya da build ve test geçmeden turun bitmesine izin vermeyen bir Stop hook, “umarım uyar” ile “uymak zorunda” arasındaki farkı yaratıyor. .NET tarafında bu, dotnet format, nullable ve uyarıları hata sayma ayarları ve Roslyn analyzer’ları demek.

Mimari kuralları da test olarak yazabiliriz. “Controller repository’ye bağımlı olamaz” kuralı NetArchTest ile şöyle yazılabilir:

[Fact]
public void Controllers_Should_Not_Depend_On_Repositories()
{
    var result = Types.InAssembly(typeof(CustomerController).Assembly)
        .That().ResideInNamespace("MyApp.Controllers")
        .ShouldNot().HaveDependencyOn("MyApp.Repositories")
        .GetResult();

    Assert.True(result.IsSuccessful,
        "Controller'lar repository'yi doğrudan çağırmaz. CustomerService üzerinden gidin.");
}

Burada küçük ama önemli bir ayrıntı var: Hata mesajı sadece “kural ihlal edildi” demiyor, ne yapılacağını söylüyor. Agent testi kırdığında bu mesajı okuyup bir sonraki adımını buradan çıkarıyor. OpenAI’ın linter’larında da aynı yaklaşım var.

Güvenlik için de aynısı geçerli. Statik analiz, bağımlılık taraması ve secret taraması agent’ın yazdığı kod için de CI’da zorunlu bir kapı olarak durmalı. Veracode’un bulguları, bu işi modelin zekâsına bırakmanın yeterli olmadığını gösteriyor.

4. Test, Agent’ın Düzenleyebildiği Bir Şey Olmamalı

Agent’a “testler geçsin” dediğimizde bazen en kısa yol testi geçirmek oluyor. Yapay zekâ laboratuvarlarının kendi değerlendirmelerinde de gözlenen bir davranış bu: Assertion’ı zayıflatmak, beklenen değeri kodun içine gömmek, hatta test çalıştırıcısından başarı koduyla çıkmak. Kötü niyet yok, agent kendisine verilen ölçütü, yani geçen testi, optimize ediyor.

Çözüm olarak üç yöntem kullanılıyor. Mevcut test dizinini bir hook ile salt okunur yapmak. CI’da, testlere dokunan ya da yeni test eklemeden kaynak kodu değiştiren PR’ları işaretlemek. Bir de agent’ın hiç görmediği ayrı bir kabul testi seti tutmak, çünkü koda gömülen değerler görülmemiş girdilerde yakalanıyor. Ortak fikir şu: Yapan ile yargılayan aynı olmamalı.

Kapsama yüzdesi de tek başına bir şey söylemiyor. Önemli olan testlerin gerçekten bir şey yakalayıp yakalamadığı. Mutation testing (.NET’te Stryker.NET gibi araçlar) bunu ölçüyor: Koda küçük hatalar sokuyor ve testlerin fark edip etmediğine bakıyor.

5. Yazan Agent İncelemesin, Parçalar da Küçük Olsun

Kendi yazdığımız kodu incelemek zordur, agent için de aynısı geçerli. Anthropic’in önerdiği writer/reviewer deseninde bir oturum kodu yazıyor, temiz bir context’e sahip ikinci bir oturum ya da alt agent yalnızca diff’i ve kriterleri görerek inceliyor. Reviewer yazanın akıl yürütmesini görmediği için sonuca kendi gözüyle bakıyor.

Anthropic’in kendisinin uyardığı bir tuzak da var: Reviewer’a “boşlukları bul” dersek her durumda bir şeyler bulur. Her bulguyu kovalarsak ortaya aşırı mühendislik çıkıyor: Gereksiz soyutlama katmanları, savunmacı kodlar, olmayacak durumlar için testler. O yüzden reviewer’dan sadece doğruluğu ve istenen gereksinimleri etkileyen boşlukları raporlamasını istemek daha sağlıklı.

İnsan review’ı bunun yerine geçmiyor, sadece odağı değişiyor. CodeRabbit’in önerdiği gibi AI kodunda sık sorun çıkan yerlere bilinçli bakmak mantıklı: Hata yönetimi, gereksiz I/O, güvenlik, isimlendirme tutarlılığı. DORA raporunun öne çıkardığı iki alışkanlık da burada işe yarıyor: Küçük parçalarla çalışmak ve düzenli commit atıp kolayca geri alabilmek. 1.200 satırlık bir diff’i kimse gerçekten okumuyor. 150 satırlık bir PR’ı okuyor, gerekirse geri alması da kolay.

6. Borç Birikir, Düzenli Temizlik Gerekir

Her şeyi iyi kursak bile küçük sapmalar zamanla birikiyor. Az önce gördüğümüz çalışmada AI’ın getirdiği sorunların yüzde 22,7’si son sürümde hâlâ duruyordu. GitClear verisinde de bir yıldan eski koda yapılan refactoring yaklaşık yüzde 74 düşmüş. Temizlik kendiliğinden olmuyor.

OpenAI’ın çözümü bu işi de agent’a vermek olmuş: Düzenli çalışan görevler kodu belirlenen kurallardan sapmalara karşı tarıyor ve bir dakikadan kısa sürede incelenebilecek küçük refactoring PR’ları açıyor. Bizim projede bu, haftalık bir agent görevi olabilir: Tekrar eden kod blokları, kullanılmayan metotlar, güncelliğini yitirmiş dokümantasyon. Büyük bir temizlik günü beklemek yerine her hafta küçük bir temizlik.

Ölçerken de hızın yanına kaliteyi koymak lazım. Kaç PR merge ettiğimize bakmak yetmiyor, değişiklik başarısızlık oranına, yeniden iş miktarına ve kod tekrarına da bakmak gerekiyor.

Hepsini Bir Araya Getirince

Baştaki Brand filtresi görevine dönelim, bu sefer ortamı kurulmuş bir agent’la.

Agent plan modunda başlıyor. Kural dosyasından filtreleme mantığının CustomerFilterBuilder’da toplandığını öğreniyor, benzer filtrelere bakıyor ve yeni mantığı oraya ekliyor. Controller’dan repository’yi çağırmaya kalktığında mimari test kırılıyor ve hata mesajı CustomerService üzerinden gitmesini söylüyor. Mevcut testlere dokunamadığı için sorunu testte değil kodda çözüyor. Build, test ve format kontrolü geçmeden turu bitiremiyor. Ayrı bir reviewer alt agent’ı bir catch bloğunun hatayı yuttuğunu yakalıyor. Sonunda ortaya 150 satırlık küçük bir PR çıkıyor ve bir insan onu gerçekten okuyabiliyor.

Üç ay sonra aynı koda baktığımızda tuzak yok. İki agent da aynı modeli, aynı repository’yi ve aynı araçları kullanıyor olabilir. Aralarındaki farkı yaratan model değil, çalıştıkları ortam.

Toparlarsak

Modeller gelişiyor ve bu sorunun bir kısmını elbette hafifletecek. Ama Veracode büyük modellerin güvenlikte küçüklerden belirgin şekilde iyi olmadığını, DORA ise AI’ın sağlam sistemi güçlendirdiği kadar zayıf sistemin zayıflığını da büyüttüğünü gösteriyor. Hangi model gelirse gelsin, ondan ne beklediğimizi ve gelen işi nasıl doğrulayacağımızı biz tanımlıyoruz.

Özetle kalite dört yerde yaşıyor: Yazılı bir tarifte, makinenin zorladığı kurallarda, bağımsız bir incelemede ve düzenli temizlikte. Bunlardan hiçbiri agent’ın iyi niyetine bağlı değil.

Baştaki soruya dönersek: Çalışan kod yeterli mi? Hayır.

İyi bir agent, iyi kod yazan agent değildir. Kötü kod yazmasının zor olduğu bir ortamda çalışan agent’tır.

Agent’lar daha fazla kod yazdıkça, o kodu okuyacak, değiştirecek ve ayakta tutacak olan hâlâ biz ve bizden sonraki agent’lar olacağız. “Çalışıyor” ile yetinmek bugün hızlı hissettirse de faturası genellikle sonradan geliyor.

Bir sonraki yazıda görüşmek üzere.

İyi çalışmalar.

Yorum bırakın