Bölüm 2 / 15
DRY, KISS, YAGNI
Aşırı mühendislik tuzağı ve sade kalmak.
12 dakikalık okuma
Önceki ders kodun okunmasını düzeltti. Bu ders bir düzey yukarı çıkıyor: ne zaman soyutlama yapılır? Yeni öğrenenlerin çoğu "tekrar etme" kuralını duyup benzer gördüğü her şeyi ortak bir metoda taşır ve birkaç ay sonra bayraklarla dolu, kimsenin dokunamadığı bir yardımcı sınıfla kalır. Kıdemin görünür olduğu yer tam burasıdır: tekrarı görmek kolaydır, hangi tekrarın gerçek tekrar olduğunu ayırt etmek zordur. Üç kısaltma — DRY, KISS, YAGNI — birlikte bu ayrımın çerçevesini kurar.
01Ne öğreneceksin
- DRY'ın gerçek tanımı: kod benzerliği değil, bilginin tek temsili
- Tesadüfi tekrar ve yanlış soyutlamanın neden tekrardan pahalı olduğu
- KISS: özsel karmaşıklık ile arızi karmaşıklık ayrımı
- YAGNI: erken soyutlamanın maliyeti ve YAGNI'nin geçersiz olduğu durumlar
- Karar ölçütü: değişim ekseni, tekrar sayısı, geri alınabilirlik
- Sınırlar: test kodunda, mikroservislerde ve yapılandırmada DRY nasıl değişir
02DRY: "Don't Repeat Yourself"
Tekrarın maliyeti somuttur. Aynı iş kuralı üç dosyada duruyorsa, kural değiştiğinde üçünü de bulman gerekir; biri gözden kaçarsa sistem iki farklı doğruya aynı anda inanmaya başlar. Bu tür hatalar geç fark edilir ve pahalıdır, çünkü bir tarafta doğru sonuç verirken diğer tarafta yanlış verir.
Ancak DRY'ın orijinal tanımı koddan söz etmez:
"Her bilgi parçasının, sistem içinde tek, kesin ve yetkili bir temsili olmalıdır."
Kritik kelime bilgidir. Tekrar eden şey bir iş kuralı, bir hesap, bir doğrulama şartı ya da bir varsayımdır — birbirine benzeyen satırlar değil. İki kod bloğu aynı görünüyor olabilir ama farklı bilgileri temsil ediyorsa, onları birleştirmek DRY uygulamak değil, birbirinden bağımsız iki şeyi birbirine bağlamaktır.
// GERÇEK tekrar: aynı iş kuralı iki yerde
// SiparisServisi içinde
if (siparis.Tutar > 5000) indirim = siparis.Tutar * 0.10m;
// RaporServisi içinde
if (s.Tutar > 5000) tahminiIndirim = s.Tutar * 0.10m;
// Kural değişirse ikisi de değişmeli → tek yere alınmalı
// TESADÜFİ tekrar: iki farklı kural bugün aynı görünüyor
// Kullanıcı şifresi en az 8 karakter
if (sifre.Length < 8) return Hata("Şifre kısa.");
// Kupon kodu en az 8 karakter
if (kupon.Length < 8) return Hata("Kupon kodu kısa.");
// Bunları "UzunlukDogrula(x, 8)" diye birleştirmek, iki bağımsız kuralı
// birbirine bağlar: şifre politikası 12'ye çıkınca kupon da değişir
Ayırt etme sorusu tek cümledir: bu iki yer gelecekte aynı sebeple mi değişecek? Cevap evetse tekrar gerçektir, birleştir. Hayırsa ya da bilmiyorsan, bekle.
03Yanlış soyutlama, tekrardan pahalıdır
Sahada en sık görülen zarar, tekrardan değil aceleci birleştirmeden gelir. Süreç hep aynı biçimde ilerler:
- İki yerde benzer kod görülür, ortak bir metoda alınır.
- Üçüncü çağıran biraz farklı davranış ister; metoda bir
boolparametre eklenir. - Dördüncü çağıran başka bir fark ister; ikinci bir bayrak eklenir, içeride
if'ler dallanır. - Bir süre sonra metot, hiçbir çağıranın tam olarak ihtiyaç duymadığı ama hepsinin bağlı olduğu bir düğüme dönüşür. Değiştirmek riskli olduğu için kimse dokunmaz; herkes etrafından dolaşır.
// beşinci bayrak eklendiğinde artık kimse bu metodun ne yaptığını bilmiyor
public string Bildirim(Kullanici k, bool epostaMi, bool kisaMetin,
bool imzaEkle, bool testModu, string? sablonAdi = null)
Bu duruma yanlış soyutlama denir. Sonucu, tekrarın olduğundan daha kötüdür: tekrarda her kopya bağımsızdır ve yalnız kendi bağlamını bozar; yanlış soyutlamada tek bir değişiklik, birbiriyle ilgisi olmayan bütün çağıranları etkiler. Sandi Metz'in sahada çok tekrarlanan kuralı bunu özetler: "tekrar, yanlış soyutlamadan çok daha ucuzdur."
Yanlış soyutlamayı fark ettiğinde doğru hamle, üstüne bir bayrak daha eklemek değil, geri açmaktır: soyutlamayı çağıranların içine geri kopyala, her çağıran kendi ihtiyacına göre sadeleşsin, sonra gerçekten ortak kalan çekirdeği yeniden çıkar.
Pratikte kullanılan eşik üç kuralıdır (rule of three): ilk tekrarda kopyala, ikinci tekrarda not al, üçüncü tekrarda soyutla. Sebebi istatistiksel değildir — üçüncü örnek geldiğinde, neyin sabit neyin değişken olduğunu artık tahmin etmek zorunda kalmazsın, görüyorsundur.
04KISS: karmaşıklığın iki türü
KISS ("Keep It Simple, Stupid") kabaca "gereksiz karmaşıklık üretme" demektir. Ayrımı yapılabilir hâle getirmek için karmaşıklığı ikiye bölmek gerekir:
- Özsel karmaşıklık — problemin kendisinden gelir. KDV oranları ürün tipine göre değişiyorsa, bu karmaşıklık koddan kaldırılamaz; yalnız düzenli biçimde ifade edilebilir.
- Arızi karmaşıklık — çözümü sen ürettin. Gereksiz katmanlar, kullanılmayan genellik, yansıma (reflection) hüneri, üç seviyeli generic yardımcı sınıflar. Bunlar kaldırılabilir ve KISS'in hedefi tam olarak bunlardır.
"Akıllı kod" arızi karmaşıklığın en sevimli biçimidir. Yazarken tatmin eder, okurken bedel ödetir:
// akıllı: tek satırda yansımayla alan eşleme
var dto = typeof(T).GetProperties()
.Where(p => p.CanWrite)
.Aggregate(new T(), (acc, p) => { p.SetValue(acc, kaynak[p.Name]); return acc; });
// sade: on satır, ama derleyici doğruluyor, hata ayıklanabiliyor, IDE takip edebiliyor
var dto = new MusteriDto
{
Ad = kaynak.Ad,
Email = kaynak.Email,
KayitTarihi = kaynak.KayitTarihi
};
İkinci sürüm daha uzundur ama toplam maliyeti düşüktür: derleme zamanında doğrulanır, alan adı değişince derleyici uyarır, hata ayıklayıcıda adım adım izlenir, yeni gelen okur. Basitlik satır sayısı değil, anlaşılma ve değiştirilme kolaylığıdır.
KISS'in kötüye kullanıldığı bir yer de vardır: gerekli hata yönetimini, doğrulamayı ya da eşzamanlılık korumasını "sadelik" adına atlamak KISS değil, eksik iştir. Özsel karmaşıklığı silmeye çalışmak, onu kullanıcıya ya da üretim ortamına aktarır.
05YAGNI: ihtiyacın olmayacak
YAGNI ("You Aren't Gonna Need It"), bugünkü gereksinim tarafından talep edilmeyen kodu yazmama disiplinidir. Gerekçe basittir: gelecek tahminleri genelde tutmaz; tutmadığında elinde yalnız boşa harcanmış zaman değil, aynı zamanda bakılması, test edilmesi ve anlaşılması gereken fazladan kod kalır.
Tipik YAGNI ihlalleri:
| Belirti | Gerekçe olarak duyulan cümle | Gerçekte olan |
|---|---|---|
Tek uygulaması olan arayüz (IXServisi + XServisi) | "İleride başka uygulama gelirse" | Her değişiklik iki dosyada; ikinci uygulama çoğu zaman hiç gelmez |
| Gereksiz yapılandırma anahtarı | "Belki değiştirmek isteriz" | Test edilmemiş kod yolları ve dokümante edilmemiş ayar |
| Erken generic tip | "Başka tiplerle de çalışsın" | Tek tiple kullanılan, okunması zor imzalar |
| Kullanılmayan genişletme noktası (hook, event) | "Esnek olsun" | Hiç tetiklenmeyen kod, yanlış güvenlik hissi |
| Erken mikroservis bölünmesi | "Ölçeklenebilir olsun" | Ağ sınırları, dağıtık hata ayıklama, gereksiz operasyon yükü (Hat 19) |
YAGNI'nin geçerli olmadığı durumu bilmek, ilkeyi bilmek kadar önemlidir. Ölçüt geri alınabilirliktir:
- Ucuz geri alınabilen kararlar (bir sınıfın iç yapısı, bir metodun imzası, kullanılan yardımcı kütüphane) → YAGNI uygula, en sade hâlini yaz, gerektiğinde değiştir.
- Pahalı geri alınan kararlar → önden düşün. Veritabanı şeması ve veri modeli (üretimde veri birikince göç maliyeti yükselir), dışarıya açtığın API sözleşmesi (istemciler bağlanır), güvenlik ve kimlik modeli, kişisel veri saklama kararları, ölçülebilirlik/loglama altyapısı. Bunlarda "sonra ekleriz" pahalıya mal olur.
Aynı şekilde YAGNI, test yazmamak ya da hata yönetimini atlamak için gerekçe değildir; bunlar gelecekteki bir özellik değil, bugünkü işin parçasıdır.
06Ortak kök: önce kanıt, sonra soyutlama
3. hattaki iki dersin sonunda bıraktığımız kurallar aslında bu dersin özel hâlleriydi.
Birincisi extension metot süzgeciydi: extension, erişemediğin tipler içindir; kendi sınıfına yazabiliyorsan yaz, durum tutması gerekiyorsa sınıf kur, testte değiştirilecekse arayüz kullan. Buradaki ortak fikir, aracın gücünün değil problemin aracı seçmesidir.
İkincisi struct kuralıydı: "struct, ölçülmüş bir
sıcak noktanın optimizasyonudur." Yani ölçüm olmadan yapılan performans kararı
tahmindir; tahmine dayalı optimizasyon, kodu okunmaz yaparken kazanç
sağlamayabilir.
Bu ikisi ve YAGNI aynı kalıbı paylaşır: erken optimizasyon ile erken soyutlama kardeştir. Her ikisi de bugünkü kanıt yerine gelecek tahminine dayanır, her ikisi de kodu bugün karmaşıklaştırır ve her ikisi de yanlış çıktığında geri alınması zordur.
Uygulanabilir sıra şudur:
- Çalışan, sade sürümü yaz.
- İhtiyacın (ikinci ya da üçüncü kullanım, ölçülmüş darboğaz, gerçek gereksinim) ortaya çıkmasını bekle.
- Kanıt geldiğinde soyutla ya da optimize et — artık neyin değişken olduğunu biliyorsun.
Bu sıranın bir istisnası vardır ve bir sonraki derslerde göreceğin SOLID ilkeleriyle ilgilidir: zaten bildiğin bir değişim ekseni varsa (örneğin ödeme sağlayıcısının değişeceği ilk günden belliyse), o eksende soyutlama YAGNI ihlali değildir. Fark, "belki lazım olur" ile "bunun olacağını biliyoruz" arasındadır.
07Sınırlar ve karar ölçütü
Kod incelemesinde kullanılabilir bir kontrol listesi:
| Soru | Evet ise | Hayır ise |
|---|---|---|
| İki kopya aynı sebeple mi değişir? | Gerçek tekrar — birleştir | Tesadüfi tekrar — bırak |
| Üçüncü örnek geldi mi? | Değişkeni artık görüyorsun, soyutla | Bekle, kopyayı taşı |
| Soyutlama bayrak parametresi gerektiriyor mu? | Yanlış soyutlama uyarısı — ayır | Devam |
| Kararı geri almak pahalı mı? | Önden tasarla | YAGNI uygula |
| Karmaşıklık problemden mi geliyor? | Özsel — düzenli ifade et | Arızi — sil |
Üç bağlamda kurallar bilerek farklılaşır:
- Test kodunda okunabilirlik, tekrarsızlıktan önce gelir. Aşırı DRY yapılmış testler, başarısız olduğunda "neyin bozulduğunu" göstermez; okuyucu yardımcı metotların içine dalmak zorunda kalır. Testte her senaryonun kendi kurulumunu açıkça yazması genelde tercih edilir (15. hat bunu ayrıntılandırır).
- Servis sınırlarında DRY, bağımlılık üretir. İki servisin ortak kodunu paylaşılan bir kütüphaneye almak, onları tek dağıtım takvimine bağlar; bağımsız dağıtılabilirlik mikroservislerin varlık sebebiyken bu ciddi bir bedeldir. Bu yüzden dağıtık sistemlerde bir miktar tekrar bilinçli olarak kabul edilir (Hat 19).
- Bilgi tekrarı yalnız kodda olmaz. Aynı sabitin hem koda hem yapılandırma dosyasına, aynı şemanın hem SQL betiğine hem sınıfa yazılması da DRY ihlalidir — ve bunlar derleyicinin yakalayamadığı, sessiz kalan türdendir.
Bir sonraki ders SOLID'in ilk harfiyle başlıyor: tek sorumluluk. Oradaki "değişmek için tek sebep" ölçütü, bu derste kullandığın "aynı sebeple mi değişir" sorusunun sınıf düzeyindeki karşılığıdır.
08Mini alıştırma — Soyutlamayı geri açmak
Yaklaşık 20 dakika · dotnet run
- Yanlış soyutlamayı üret: Aşağıdaki metodu yaz ve üç çağıranını
(kullanıcı kaydı, sipariş onayı, şifre sıfırlama) kur.
Şimdi dördüncü bir ihtiyaç ekle: SMS bildirimlerinde 160 karakter sınırı olacak. Bunu mevcut imzaya sığdırmayı dene ve imzanın nasıl büyüdüğünü gör.public string Bildirim(string ad, string icerik, bool epostaMi, bool imzaEkle, bool testModu) - Geri aç: metodu sil, üç çağıranın her birinin kendi kısa metnini kendi içinde üretmesini sağla. Toplam satır sayısı arttı mı? Her çağıranın okunabilirliği ne oldu?
- Gerçek ortak çekirdeği bul: üç çağıranda gerçekten aynı sebeple
değişecek olan tek şey nedir (ipucu: gönderim işi mi, metin üretimi mi?). Yalnız
onu bir arayüzün arkasına al:
IBildirimGonderici.Gonder(Bildirim b). - Tesadüfi tekrarı ayır: şifre uzunluğu (8) ve kupon kodu uzunluğu (8)
kontrollerini tek bir
UzunlukDogrulametoduna al. Sonra "şifre politikası 12 karaktere çıktı" gereksinimini uygula ve bu değişikliğin kupon doğrulamasını nasıl kırdığını gözle. İki kuralı ayır. - KISS ölçümü: yansımayla alan eşleyen kısa sürümü ve elle yazılmış eşlemeyi yan yana koy. Kaynak sınıfa yeni bir özellik ekle, birinde derleyici seni uyarıyor mu? Alan adını değiştir, hangisi çalışma zamanında patlıyor?
- YAGNI denetimi: yazdığın koddaki tek uygulaması olan arayüzleri listele. Her biri için sor: bu arayüz testte taklit (mock) için mi var, gerçek bir ikinci uygulama için mi, yoksa alışkanlıktan mı? Sadece üçüncü gruptakileri kaldır.
09Kendini sına
DRY'ın gerçek tanımı nedir? Kod benzerliğiyle farkı ne?
DRY, "her bilgi parçasının sistemde tek, kesin ve yetkili bir temsili olmalıdır" der. Anahtar kelime bilgidir: tekrar eden şey bir iş kuralı, bir hesap ya da bir doğrulama şartıdır; birbirine benzeyen satırlar değil. İki kod bloğu aynı görünse de farklı bilgileri temsil ediyorsa buna tesadüfi tekrar denir ve birleştirilmemelidir — şifre uzunluğunun ve kupon kodu uzunluğunun ikisinin de 8 olması onları aynı kural yapmaz; şifre politikası değişince kupon doğrulaması da kırılır. Ayırt etme sorusu tek cümledir: bu iki yer gelecekte aynı sebeple mi değişecek? Evetse gerçek tekrardır, birleştirilir; hayırsa ya da belirsizse beklenir.
Yanlış soyutlama neden tekrardan pahalıdır? Nasıl düzeltilir?
Aceleci birleştirme tipik bir yol izler: iki benzer blok ortak metoda alınır, üçüncü çağıran fark isteyince bool bayrak eklenir, dördüncüsüyle ikinci bayrak gelir ve metot içeride dallanır. Sonuçta hiçbir çağıranın tam olarak istemediği ama hepsinin bağlı olduğu bir düğüm oluşur; değiştirmek riskli olduğu için etrafından dolaşılır. Tekrarda her kopya bağımsızdır ve yalnız kendi bağlamını etkiler, yanlış soyutlamada ise tek değişiklik ilgisiz bütün çağıranlara yayılır — bu yüzden tekrar daha ucuzdur. Düzeltme yolu bayrak eklemek değil geri açmaktır: soyutlamayı çağıranların içine geri kopyala, her biri kendi ihtiyacına göre sadeleşsin, sonra gerçekten ortak kalan çekirdeği yeniden çıkar. Pratik eşik üç kuralıdır; üçüncü örnekte neyin sabit neyin değişken olduğu tahmin edilmez, görülür.
Özsel ve arızi karmaşıklık nedir? KISS hangisini hedefler?
Özsel karmaşıklık problemin kendisinden gelir: KDV oranları ürün tipine göre değişiyorsa bu karmaşıklık koddan kaldırılamaz, yalnız düzenli ifade edilebilir. Arızi karmaşıklık ise çözümün kendisinden gelir — gereksiz katmanlar, kullanılmayan genellik, yansıma hüneri, erken generic yapılar. KISS arızi karmaşıklığı hedefler. Klasik örnek, yansımayla tek satırda yapılan alan eşlemenin elle yazılmış eşlemeye yeğlenmesidir: kısa sürüm yazarken tatmin eder ama derleme zamanı doğrulamasını, hata ayıklanabilirliği ve editör desteğini kaybettirir; toplam maliyeti yüksektir. Basitlik satır sayısı değil, anlaşılma ve değiştirilme kolaylığıdır. KISS'in kötüye kullanımı, gerekli hata yönetimini ya da eşzamanlılık korumasını "sadelik" adına atlamaktır — bu sadelik değil eksik iştir.
YAGNI ne zaman uygulanır, ne zaman uygulanmaz?
YAGNI, bugünkü gereksinimin talep etmediği kodu yazmama disiplinidir; gerekçesi tahminlerin tutmaması ve tutmadığında geriye bakımı, testi ve okunması gereken fazladan kod kalmasıdır. Tipik ihlaller: tek uygulaması olan arayüzler, kullanılmayan yapılandırma anahtarları, erken generic tipler, hiç tetiklenmeyen genişletme noktaları ve erken mikroservis bölünmesi. Ayrım ölçütü geri alınabilirliktir: bir sınıfın iç yapısı ya da metot imzası gibi ucuz geri alınan kararlarda en sade hâl yazılır; veri şeması, dışarıya açılan API sözleşmesi, güvenlik ve kimlik modeli, kişisel veri saklama ve gözlemlenebilirlik gibi pahalı geri alınan kararlarda önden düşünülür. YAGNI test yazmamak ya da hata yönetimini atlamak için gerekçe değildir. Bir de bilinen değişim ekseni istisnası vardır: ödeme sağlayıcısının değişeceği ilk günden belliyse o eksende soyutlama yapmak YAGNI ihlali sayılmaz — fark "belki lazım olur" ile "olacağını biliyoruz" arasındadır.
10Sınav köşesi
Yazılıda nasıl sorulur
(1) Açılım ve tanım: DRY, KISS, YAGNI kısaltmaları ne demek, birer cümleyle açıkla. (2) Kod okuma: iki benzer blok verilip "DRY uygulanmalı mı?" sorulur — beklenen cevap "aynı sebeple değişiyorlarsa evet" ayrımıdır. (3) Kavram: "yanlış soyutlama neden tekrardan pahalıdır?" (4) Üç kuralı (rule of three) nedir, neden üç. (5) Doğru/yanlış: "her arayüzün tek uygulaması olsa bile yazılmalıdır" (yanlış); "YAGNI test yazmamayı da kapsar" (yanlış); "veritabanı şeması kararı sonraya bırakılmalıdır" (yanlış — geri alınması pahalı). (6) Ayrım: özsel ve arızi karmaşıklık örnekleriyle. (7) Uygulama: bayrak parametreleriyle şişmiş bir metot verilip nasıl ayrıştırılacağının yazılması.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"DRY'ı nasıl uygularsınız?" için düzen: (1) Doğru tanım — "DRY kod benzerliğiyle değil bilgiyle ilgilidir: bir iş kuralının sistemde tek yetkili temsili olmalı. Kararı verirken sorduğum soru şu: bu iki yer aynı sebeple mi değişecek?" (2) Karşı taraf — "aynı sebeple değişmiyorlarsa birleştirmem; tesadüfi tekrarı birleştirmek iki bağımsız kuralı birbirine bağlar. Sahada tekrardan çok yanlış soyutlamadan zarar gördüm: bayraklarla şişen, kimsenin dokunamadığı ortak metotlar." (3) Pratik eşik — "üç kuralı uygularım; üçüncü örnekte neyin değişken olduğunu tahmin etmem, görürüm. Yanlış soyutlama fark edersem bayrak eklemem, geri açarım." (4) KISS/YAGNI — "karmaşıklığı özsel ve arızi diye ayırırım; arızi olanı silerim. Bugün talep edilmeyen esnekliği yazmam, ama kararın geri alınması pahalıysa — veri şeması, dış API sözleşmesi, güvenlik modeli — önden düşünürüm." (5) Kıdem vuruşu — "erken soyutlama ile erken optimizasyon aynı hatanın iki yüzü: ikisi de kanıt yerine tahmine dayanıyor. Testte ise okunabilirliği tekrarsızlığın önüne koyarım, servis sınırlarında da bir miktar tekrarı bilinçli kabul ederim çünkü paylaşılan kütüphane bağımsız dağıtımı bitirir."
