Enstrümantasyon ya da Ölüm: Android Projesinde Gemini CLI'ı Nasıl Evcilleştirdim
yazan: Nikolay Vlasov
Kod üretmek artık çok kolay hale geldi.
Anlatmaya değer bir deneyimim oldu: Bir yan projede, neredeyse hiç inceleme yapmadan, "ajan ne yazdıysa kabul ediyoruz" prensibiyle bir tür "vibe-coding" denedim. Başlangıçta bu yöntem çok hızlı ve kullanışlı görünüyordu. Ancak oldukça kısa bir süre sonra proje dağılmaya başladı: Kod hantal ve sürdürülemez hale geldi, bazı işlevler bozuldu; gecikmeler, bellek sızıntıları ve normalde aylarca süren birikmesi gereken tüm sorunlar neredeyse anında ortaya çıktı.
Bir noktada şu gerçek netleşti: Sorun ajanın "kötü" kod yazması değil. Sorun, bu kodun kontrolsüz bir şekilde birikme hızıydı.
Bu Yaklaşımda Neler Bozuluyor?
Manuel kontrolü kaldırdığınızda, tipik desenler kendini göstermeye başlar:
- Mimari katmanların atlanması;
- Mantık tekrarları;
- Modül sınırlarının belirsizleşmesi;
- Tesadüfi bağımlılıklar;
- Kodun gereksiz yere karmaşıklaşması;
- Performansın kademeli olarak düşmesi.
Önemli olan, tüm bunların bir anda gerçekleşmemesi; her bir adım "normal" görünür, ancak toplamda sistem hızla kontrolden çıkar.
Neden Tek Bir Prompt Yeterli Gelmedi?
İlk denemem oldukça belirgindi; şunları içeren büyük bir rules.md hazırladım:
- Mimari kısıtlamalar;
- İsimlendirme kuralları (naming conventions);
- Katman kuralları;
- Projenin yerel kuralları.
Bu kısmen yardımcı oldu ancak sorunu çözmedi.
Pratikte şunlar ortaya çıktı:
- Uzun bağlam (context) istikrarsız çalışıyor;
- Kuralların bir kısmı zamanla görmezden geliniyor;
- Model, kısıtlamaları her zaman tutarlı bir şekilde uygulamıyor;
- Prompt büyüdükçe maliyet ve yanıt süresi artıyor.
Sonuç olarak pragmatik bir sonuca vardım: Önemli kurallar sadece tanımlanmamalı, aynı zamanda kontrol edilmelidir.
1. Konsist: Mimari, Yürütülebilir Bir Sözleşme Olarak
Ajan, varsayılan olarak en kolay uygulama yolunu seçer. Eğer bir katmanı atlamak mümkünse, bunu yapacaktır.
Bunu kısıtlamak için, mimariyi Konsist (Kotlin kodunun mimari kurallarını testler aracılığıyla kontrol eden bir araç) kullanarak testler üzerinden tanımlamaya başladım.
Örnek:
@Test
fun `use cases should have UseCase suffix and reside in domain package`() {
val classes = Konsist.scopeFromProject().classes()
val violations = classes
.filter { classDeclaration ->
classDeclaration.name?.endsWith("UseCase") != true ||
!classDeclaration.resideInPackage("com.core.domain")
}
if (violations.isNotEmpty()) {
val message = buildString {
appendLine("VIOLATION: UseCase naming conventions not followed")
appendLine("FIX: Rename class to end with 'UseCase'")
appendLine("FIX: Move class to com.core.domain package")
}
fail(message)
}
}
Burada benim için iki şey önemli:
- Kontrol bir ihlali tespit ediyor;
- Mesaj, bunun nasıl düzeltileceğine dair net bir yön veriyor.
Ajan artık "boşlukta" değil, bir geri bildirim döngüsü içinde çalışmaya başlıyor.
2. Günlük Sıkıştırma: Daha Az Gürültü, Daha Hızlı İterasyonlar
Karşılaştığım sorunlardan biri de günlüklerin (logs) hacmiydi.
Ajanın önüne tam Gradle veya JUnit çıktısı koyduğunuzda, bu hacmin içinde kayboluyor. Bağlam, sinyal yerine gürültüyle doluyor.
Bu nedenle, basit bir sıkıştırma katmanı oluşturdum:
- Sadece başarısız olan testleri bırakıyorum;
- Kısa bir hata mesajı alıyorum;
- Stacktrace'i kısıtlıyorum;
- Gereksiz olan her şeyi çıkarıyorum.
Örnek:
def parse_xml_reports(root_dir):
summary = []
for testcase in root.findall(".//testcase"):
failure = testcase.find("failure")
if failure is not None:
message = failure.get("message", "No message")
text = failure.text or ""
stacktrace = "\n".join(text.strip().split("\n")[:15])
summary.append(f"FAILED: {testcase.get('name')}")
summary.append(f"Message: {message}")
summary.append(f"Stacktrace:\n{stacktrace}\n")
return summary
Bundan sonra "bozuldu → düzeltildi" döngüsü belirgin şekilde daha hızlı ve öngörülebilir hale geldi.
3. Spotless ve Detekt: İnsan Müdahalesi Olmadan Temel Hijyen
Bir sonraki katman otomatize edilmiş kalite kontrolleri:
- Spotless (kodun otomatik formatlanması için çok amaçlı bir araç);
- detekt (Kotlin'deki potansiyel sorunları ve karmaşık yapıları bulmak için statik analizör).
Bunları "ek araçlar" olarak görmeyi bıraktım; artık sürecin bir parçasılar.
Eğer kod bu kontrollerden geçmiyorsa, tamamlanmış sayılmıyor. Ajan kendi kendine döner ve düzeltir.
Bu şunları ortadan kaldırır:
- İncelemelerdeki ufak tefek düzeltmeler;
- Stil tartışmaları;
- Okunabilirliğin kademeli olarak bozulması.
4. Çeviriler: Mekanik Hataların Giderilmesi
strings.xml beklemediğimiz bir sorun noktası oldu.
LLM'ler şuralarda düzenli olarak hata yapıyorlar:
- Kesme işaretleri (apostrophes);
- Tırnak işaretleri;
- Kaçış dizileri (escape sequences).
Modeli metin üzerinden bu konuda "eğitmeye" çalışmadım. Şunları eklemek daha kolay oldu:
- Kontrol mekanizması;
- Otomatik düzeltme;
- XML ayrıştırma (parsing) üzerinden çalışma.
Önemli: Kaba küresel değişimler (global replaces) yerine sadece noktasal düzeltmeler.
5. Performans ve Boyut
Zamanla ortaya çıkan ayrı bir etki de, ürünün fark edilmeyen yavaş bozulmasıdır.
Ajan bir görevi çözebilir ancak bunu yaparken:
- Ağır bir bağımlılık ekleyebilir;
- Kodu karmaşıklaştırabilir;
- Derleme (build) boyutunu artırabilir;
- Performansı etkileyebilir.
Bunu takip etmek için şunları ekledim:
- Temel benchmark'lar;
- Derleme boyutu kontrolü.
Bu tam bir garanti sağlamasa da, en azından sapmaları zamanında görmemizi sağlar.
6. Pre-commit: Hızlı Filtre (Mercurial/hg veya Git)
Bazı kontrolleri pre-commit aşamasına çektim:
- Otomatik formatlama;
- Noktasal linting;
- Kritik modüller için mimari kontroller.
Buradaki fikir "her şeyi yasaklamak" değil:
- Temel sorunları hızla yakalamak;
- Bunları repoya taşımamak;
- İncelemeleri (reviews) gereksiz gürültüyle yormamak.
Bu Neden Çalışıyor?
Geliştirmenin özü aslında pek değişmedi. Testler, linter'lar ve mimari kısıtlamalar her zaman standarttı.
Değişen şey başka: Kodun oluşturulma hızı.
Değişiklikler hızlı ve büyük hacimlerde üretildiğinde, manuel kontrol artık ölçeklenemiyor. Eskiden "gözle yakalanabilecek" şeyler artık işleme hızı yetmediği için kaçabiliyor.
Bu bağlamda, otomatize edilmiş kontroller bir "iyi uygulama" (good practice) olmaktan çıkıp, sistemi dengede tutmak için temel bir gereklilik haline geliyor.
Sonuç
Bu yaklaşımı herkes için evrensel veya zorunlu olarak görmüyorum.
Ancak benim durumumda, özellikle bilişsel yükü azaltarak oldukça somut bir etki yarattı.
Artık şunlara ihtiyacım yok:
- Tüm kısıtlamaları zihnimde tutmak;
- Her değişikliği manuel olarak okumak;
- Dev günlük dosyalarını incelemek;
- Temel şeyleri sürekli tekrar kontrol etmek.
Sistem bunu üstleniyor, ben ise gerçekten anlamlı olduğu noktalarda dahil oluyorum.
Kendim için not ettiğim pratik avantajlardan bazıları:
- Rastgele regresyonlar olmadan daha istikrarlı iterasyonlar;
- Geri bildirim yoluyla ajanın öngörülebilir davranışı;
- Daha hızlı düzeltme döngüsü;
- İncelemelerde daha az gürültü;
- Ajan kullanımını ölçeklendirmenin kolaylaşması.
Bu tüm sorunları çözmez ancak ajanla çalışma sürecini belirgin şekilde daha yönetilebilir kılar — en azından benim deneyimimde.
Yararlı Bağlantılar
- Konsist — Kotlin için güncel mimari lint dokümantasyonu.
- Spotless — Kodun otomatik formatlanması için bir araç.
- detekt — Kotlin için statik kod analizörü.
- Mercurial (hg) — Versiyon kontrol sistemi.
- Maestro — Mobil uygulama UI testi otomasyon platformu.
Bir sonraki makalede, bu sürece UI senaryolarının otomatik geçişini ve arayüz kontrolünü MCP Mobile ile Maestro (mobil uygulamaların basit ve deklaratif UI testi için framework) üzerinden nasıl eklediğimi anlatacağım.