SOLID prensiplerini ilk öğrendiğimde tanımları ezberlemiştim ama gerçek kodda nasıl göründüklerini tam kavrayamamıştım. Yıllar içinde gerçek projelerde bu prensipleri uygularken edindiğim somut örnekleri paylaşmak istiyorum.
Tek Sorumluluk (S): Bir Sınıf, Bir Değişim Sebebi
Bir OrderService sınıfının hem sipariş oluşturma mantığını hem e-posta gönderme mantığını hem de loglama detaylarını barındırdığını sık görüyorum. Bunları OrderCreator, IEmailSender, ILogger gibi ayrı sorumluluklara böldüğümde, her birini bağımsız olarak değiştirebiliyor ve test edebiliyorum.
Açık/Kapalı (O): Yeni Davranış İçin Yeni Kod
Ödeme yöntemi eklerken mevcut if/else zincirine yeni bir dal eklemek yerine, IPaymentMethod arayüzünü implemente eden yeni bir sınıf yazıyorum. Böylece mevcut, test edilmiş kodu değiştirmeden yeni davranış ekleyebiliyorum.
Liskov Yerine Geçme (L): Alt Sınıf, Üst Sınıfın Sözünü Bozmamalı
Bir ReadOnlyRepository'nin IRepository'den türeyip Add() metodunda exception fırlatması, klasik bir Liskov ihlali. Bunun yerine IReadRepository ve IWriteRepository gibi ayrı, daha küçük arayüzler tanımlamak bu tuzağı önlüyor.
Arayüz Ayrımı (I): Şişman Arayüzlerden Kaçınmak
Tek bir IUserService arayüzünde 20 metod tanımlamak yerine, ilgi alanına göre IUserAuthenticator, IUserProfileManager gibi küçük arayüzlere bölmek, her implementasyonun sadece ihtiyacı olan metotlara bağımlı olmasını sağlıyor.
Bağımlılık Tersine Çevirme (D): Somut Değil, Soyut Olana Bağımlı Olmak
Bu, Clean Architecture yazımda da detaylandırdığım prensip: üst seviye modüller (Application) alt seviye detaylara (Infrastructure) değil, arayüzlere bağımlı olmalı. Bu sayede veritabanı ya da dış servis değiştiğinde iş mantığı etkilenmiyor.
Sonuç
SOLID prensiplerini her satırda harfiyen uygulamaya çalışmak bazen aşırı mühendisliğe yol açabiliyor. Benim yaklaşımım, bu prensipleri bir kural kitabı değil, "bu kod değişmesi gerektiğinde ne kadar acı verecek?" sorusuna cevap arayan bir pusula olarak kullanmak.