Clean Code ve SOLID: Orta Seviye Geliştiriciler İçin Uygulanabilir Rehber
Kodun çalışması ile kodun sürdürülebilir olması iki farklı problemdir. Clean Code ve SOLID, ikinci problemi ele alır: değişiklik maliyetini düşürmek, hatayı erken yakalamak ve ekibin kodun tamamını kafasında tutma zorunluluğunu azaltmak. Bu rehber, bu iki yaklaşımı günlük iş akışına nasıl çevireceğine odaklanır.
Önkoşullar ve kapsam
- Nesne yönelimli temel kavramlar (sınıf, arayüz, kalıtım, kompozisyon) hakkında çalışma bilgisi.
- Bir test çatısıyla (JUnit, pytest, Jest, PHPUnit vb.) en az bir kez test yazmış olmak.
- Kod örnekleri dil bağımsız kalıplar üzerinden anlatılır; sürüme bağlı bir API kullanılmaz. Bu nedenle belirli bir dil sürümü veya kütüphane sürümü belirtilmez.
Kapsam dışı: mimari desenlerin (DDD, hexagonal, event-driven) ayrıntılı tartışması ve performans optimizasyonu. Bu rehber, sınıf ve fonksiyon ölçeğindeki kararlara odaklanır.
Clean Code: Neye ve neden bakılır
Clean Code bir stil kılavuzu değil, okunabilirlik ve değiştirilebilirlik üzerine bir dizi karardır. Pratikte en çok fark yaratan başlıklar şunlardır:
1. İsimlendirme
İsim, değişkenin ne tuttuğunu ve fonksiyonun ne yaptığını açıklamalıdır. Kısaltma ve tek harfli isimler yalnızca çok kısa ömürlü, bağlamı belli yerlerde (döngü sayacı gibi) kabul edilebilir.
Karşılaştırma:
// Zayıf
int d; // geçen gün sayısı
// Daha iyi
int elapsedDays;
Fonksiyon isimleri fiil içermelidir: calculateTotal, sendInvoice, parseConfig. Boolean dönen fonksiyonlar is, has, can gibi ön eklerle okunur: isExpired(), hasPermission(user).
Yaygın hata: aynı kavram için farklı isimler kullanmak (user, account, customer aynı şeyi temsil ediyorsa). Bu, okuyucuyu gereksiz yere farklı kavramlar aradığına inandırır.
2. Fonksiyon boyutu ve tek sorumluluk
Bir fonksiyon tek bir iş yapmalı ve bunu isminden anlaşılır kılmalıdır. Pratik bir test: fonksiyonu tarif ederken "ve" bağlacı kullanıyorsan, büyük olasılıkla iki iş yapıyorsun.
// Zayıf: doğrulama, hesaplama ve kaydetme iç içe
function processOrder(order) {
if (!order.items || order.items.length === 0) throw new Error("empty");
let total = 0;
for (const item of order.items) total += item.price * item.qty;
order.total = total;
db.save(order);
mailer.send(order.userEmail, "Sipariş alındı");
}
Bu fonksiyonun test edilmesi için veritabanı ve e-posta servisi gerekir. Ayrıştırıldığında:
function validateOrder(order) { /* yalnızca doğrulama */ }
function calculateTotal(items) { /* yalnızca hesaplama */ }
function saveOrder(order) { /* yalnızca kalıcı hale getirme */ }
function notifyOrderReceived(order) { /* yalnızca bildirim */ }
Beklenen davranış: calculateTotal saf (pure) hale gelir; aynı girdiye her zaman aynı çıktıyı verir ve yan etkisi yoktur. Bu tür fonksiyonlar test edilmesi en kolay olanlardır.
Yaygın hata: "ileride gerekir" diye parametre eklemek. Kullanılmayan parametre, çağıran taraf için belirsizlik yaratır.
3. Yorumlar ve ölü kod
Yorum, kodun ne yaptığını değil neden yaptığını açıklamalıdır. Kodun kendisi zaten "ne" olduğunu söyler.
// Zayıf: kodu tekrar eden yorum
// i'yi 1 artır
i++;
// Daha iyi: kararın gerekçesi
// Sağlayıcı API'si 429 döndüğünde tekrar denemek yerine bekliyoruz;
// aksi halde kota daha hızlı tükeniyor.
Ölü kod (kullanılmayan fonksiyon, devre dışı bırakılmış blok), sürüm kontrolünde zaten saklıdır. Kod tabanında bırakmak, okuyucuya "bu hâlâ kullanılıyor olabilir" sinyali verir.
SOLID: Beş prensip, beş pratik karar
SOLID, sınıf ve modül seviyesindeki bağımlılıkları yönetmek için bir çerçevedir. Beş prensibi tek tek ele alalım.
S — Single Responsibility Principle (Tek Sorumluluk)
Bir sınıfın değişmesi için tek bir nedeni olmalıdır. "Neden" kelimesi önemli: sorumluluk, işlev sayısı değil, değişim eksenidir.
Örnek: Bir Report sınıfı hem raporu oluşturuyor hem PDF'e çeviriyor hem e-posta atıyorsa, üç farklı değişim nedeni vardır (iş kuralları, çıktı formatı, bildirim kanalı). Bunları ayırmak, birinde yapılan değişikliğin diğerlerini kırma riskini ortadan kaldırır.
O — Open/Closed Principle (Açık/Kapalı)
Yeni davranış eklemek için mevcut kodu değiştirmek yerine genişletmek tercih edilir. Bu, tipik olarak arayüz + implementasyon ayrımıyla sağlanır.
interface Discount {
apply(amount: number): number;
}
class NoDiscount implements Discount {
apply(amount) { return amount; }
}
class SeasonalDiscount implements Discount {
constructor(private rate: number) {}
apply(amount) { return amount * (1 - this.rate); }
}
Yeni bir indirim türü eklemek için mevcut sınıflar değişmez; yeni bir sınıf eklenir. Beklenen davranış: apply çağrısı, hangi implementasyon verilirse ona göre sonuç döner; çağıran taraf türü bilmek zorunda kalmaz.
Uyarı: Bu prensip, her şeyi arayüz arkasına saklamak anlamına gelmez. Değişim ekseni yoksa soyutlama eklemek gereksiz karmaşıklık üretir.
L — Liskov Substitution Principle (Yerine Geçebilirlik)
Alt sınıf, üst sınıfın yerine geçtiğinde davranışı bozmamalıdır. Klasik ihlal örneği: Square, Rectangle'dan türetildiğinde, genişlik/yükseklik bağımsız ayarlanabilen bir sözleşmeyi bozar.
Pratik test: üst sınıfın sözleşmesinde belirtilen ön koşulları alt sınıf gevşetmeli, son koşulları daraltmamalıdır. Alt sınıf, üst sınıfın fırlattığı istisnalardan daha geniş bir istisna fırlatıyorsa, yerine geçebilirlik büyük olasılıkla bozulmuştur.
I — Interface Segregation Principle (Arayüz Ayrımı)
Bir sınıf, kullanmadığı metotları içeren arayüzlere bağımlı olmamalıdır. Şişkin arayüzler, implementasyonları boş metotlar yazmaya zorlar.
// Zayıf
interface Worker {
work(): void;
eat(): void;
}
// Daha iyi
interface Workable { work(): void; }
interface Feedable { eat(): void; }
Bir robot işçi Workable'ı uygular, Feedable'ı uygulamaz. Beklenen davranış: work() çağrısı her iki durumda da anlamlıdır; eat() yalnızca gerçekten yiyebilen tiplerde bulunur.
D — Dependency Inversion Principle (Bağımlılığın Tersine Çevrilmesi)
Yüksek seviyeli modüller, düşük seviyeli modüllere değil, soyutlamalara bağlı olmalıdır. Pratikte bu, somut sınıfları doğrudan örneklemek yerine dışarıdan enjekte etmek anlamına gelir.
// Zayıf: sınıf, somut bir uygulamaya sıkı bağlı
class OrderService {
private repo = new MySqlOrderRepository();
}
// Daha iyi: arayüz üzerinden bağımlılık
class OrderService {
constructor(private repo: OrderRepository) {}
}
Beklenen davranış: test sırasında OrderRepository'nin sahte (fake) bir implementasyonu verilebilir; OrderService gerçek veritabanına dokunmadan doğrulanabilir.
Yaygın hata: arayüzü yalnızca tek bir implementasyon için tanımlamak. Arayüz, çağıran tarafın ihtiyacından doğmalı; implementasyondan kopyalanmamalıdır.
Adım adım uygulama sırası
Mevcut bir kod tabanında bu prensipleri uygulamak için önerilen sıra:
- Testleri kur. Refactor öncesi davranışı sabitleyen testler olmadan yapılan değişiklik, gerileme riskini artırır. Test yoksa önce karakterizasyon testleri (mevcut davranışı kaydeden testler) yaz.
- İsimleri düzelt. Küçük, düşük riskli, yüksek faydalı. Davranışı değiştirmez.
- Uzun fonksiyonları böl. Her bölme sonrası testleri çalıştır.
- Sorumlulukları ayır. Bir sınıfın birden fazla değişim ekseni varsa, sınıfları böl.
- Bağımlılıkları enjekte et. Doğrudan örneklemeleri yapıcı parametresine çevir.
- Soyutlamayı en son ekle. İki gerçek implementasyon ortaya çıkmadan arayüz tanımlamak, genellikle yanlış soyutlama üretir.
Bu sıra, "önce soyutla, sonra düzelt" yaklaşımının tersidir. Sebep: soyutlama, ancak değişim ekseni görüldükten sonra doğru şekillenir.
Yaygın hatalar
- Prensibi araçsallaştırmak. SOLID'i "her sınıfa arayüz" olarak uygulamak, kod tabanını arayüz çöplüğüne çevirir.
- Aşırı soyutlama. Tek implementasyonlu arayüz, esneklik değil dolaylılık ekler.
- Test edilebilirliği sonradan düşünmek. Sıkı bağımlılıklar, test yazmayı imkânsız hale getirir; bu da refactor'ı riskli kılar.
- Refactor'ı toplu yapmak. Tek commit'te yüzlerce satırı değiştirmek, geri almayı ve incelemeyi zorlaştırır. Küçük adımlar ve her adımda yeşil test tercih edilir.
- İsimlendirmeyi hafife almak. Yanlış isim, yanlış soyutlamanın en yaygın sebebidir.
Güvenlik etkileri
Clean Code ve SOLID doğrudan güvenlik prensipleri değildir, ancak dolaylı etkileri vardır:
- Küçük, tek sorumluluklu fonksiyonlar denetlenmesi ve gözden geçirilmesi daha kolay olduğundan, güvenlik açıklarının gözden kaçma olasılığını azaltır.
- Bağımlılık enjeksiyonu, gizli global durumu azaltır; global durum, oturum ve yetki kontrolü hatalarının sık görüldüğü yerdir.
- Arayüz ayrımı, bir sınıfın ihtiyaç duymadığı yeteneklere erişmesini engeller; bu, en az yetki (least privilege) ilkesiyle aynı yönde çalışır.
- Aşırı soyutlama ise tersine etki yapabilir: yetkilendirme kontrolünün hangi katmanda yapıldığı belirsizleşirse, kontrolün atlanması kolaylaşır.
Bu maddeler prensiplerin doğal sonucudur; tek başına güvenlik garantisi değildir. Girdi doğrulama, yetkilendirme ve sır yönetimi ayrıca ele alınmalıdır.
Özet
Clean Code okunabilirlik ve değiştirilebilirlik üzerine günlük kararlardır: isimlendirme, fonksiyon boyutu, yorum disiplini. SOLID ise bağımlılıkları yönetmek için beş kuraldır. İkisi birlikte, değişikliğin maliyetini düşürmeyi hedefler. Uygulama sırası önemlidir: önce test, sonra isim, sonra bölme, en son soyutlama. Prensibi araçsallaştırmak yerine, her kararın hangi değişim eksenini kolaylaştırdığını sor.