Takip et

BlindSpot Log 03: Tek Sorumluluk Prensibi (SRP) ile Daha Temiz ve Yönetilebilir Kod

Yazılım geliştirme dünyasında, karmaşıklığı yönetmek ve sürdürülebilir sistemler inşa etmek her zaman öncelikli bir hedeftir. Bu hedefe ulaş…

BlindSpot Log 03: Tek Sorumluluk Prensibi (SRP) ile Daha Temiz ve Yönetilebilir Kod

Yazılım geliştirme dünyasında, karmaşıklığı yönetmek ve sürdürülebilir sistemler inşa etmek her zaman öncelikli bir hedeftir. Bu hedefe ulaşmada bize yol gösteren temel prensiplerden biri de SOLID prensipleridir. Bu makalemizde, SOLID prensiplerinin ilk harfi olan Tek Sorumluluk Prensibi (Single Responsibility Principle – SRP) üzerine odaklanacak ve özellikle “BlindSpot Log” gibi bir loglama sisteminde bu prensibin nasıl uygulanabileceğini, kod kalitesini nasıl artırdığını ve gelecekteki değişikliklere nasıl daha dirençli hale getirdiğini detaylı bir şekilde inceleyeceğiz. Amacımız, SRP’nin sadece teorik bir kavram olmadığını, aynı zamanda günlük geliştirme pratiklerimizde somut faydalar sağlayan güçlü bir araç olduğunu göstermektir.

SRP Nedir? Temel Kavramlar

Tek Sorumluluk Prensibi (SRP), Robert C. Martin (Uncle Bob) tarafından ortaya konan SOLID prensiplerinin ilkidir ve yazılım tasarımında modülerliği ve esnekliği artırmayı hedefler. Temelinde, her sınıfın veya modülün yalnızca bir sorumluluğu olması gerektiğini savunur.

Tanım ve Amaç

SRP’ye göre, bir sınıfın değişmesi için yalnızca bir nedeni olmalıdır. Başka bir deyişle, bir sınıfın tek bir işi olmalı ve bu işi en iyi şekilde yapmalıdır. Bu, bir sınıfın birden fazla farklı paydaş veya aktör tarafından değiştirilme ihtiyacı duymaması gerektiği anlamına gelir. Örneğin, bir rapor sınıfı hem veriyi çekme hem de raporu formatlama sorumluluğuna sahipse, veritabanı yapısı değiştiğinde de, raporun görünümü değiştiğinde de bu sınıfın değişmesi gerekir. Bu durum, SRP’nin ihlalidir.

“Sorumluluk” Ne Anlama Gelir?

SRP bağlamında “sorumluluk”, bir sınıfın neden değişmesi gerektiğiyle ilgilidir. Bu, bir sınıfın belirli bir grup kullanıcı veya iş gereksinimi için tek bir görevi yerine getirmesi gerektiği anlamına gelir. Bir sınıfın sorumluluğu, onun işlevsel amacını ve diğer sistem bileşenleriyle olan etkileşimini tanımlar. Karmaşık bir sistemde, sorumlulukları net bir şekilde tanımlamak, sınıflar arası bağımlılıkları azaltır ve kodun anlaşılırlığını artırır.

Değişim Ekseni ve Tek Sorumluluk

SRP’yi anlamanın en iyi yollarından biri “değişim ekseni” kavramıdır. Bir sınıf, birden fazla değişim eksenine sahipse (yani birden fazla farklı nedenden dolayı değişmek zorunda kalıyorsa), SRP’yi ihlal ediyor demektir. Örneğin, bir kullanıcı sınıfı hem kullanıcı bilgilerini yönetme hem de kullanıcının veritabanına kaydedilme mantığını içeriyorsa, bu sınıfın iki değişim ekseni vardır: kullanıcı bilgilerinin yapısı değiştiğinde ve veritabanı depolama mekanizması değiştiğinde. SRP, bu eksenleri ayırarak her bir sorumluluğu ayrı bir sınıfa veya modüle atamayı önerir.

Neden SRP Önemlidir?

SRP, sadece akademik bir prensip olmaktan öte, yazılım projelerinin uzun vadeli sağlığı ve sürdürülebilirliği için kritik faydalar sunar. Bu faydalar, geliştirme sürecini kolaylaştırır ve hataları azaltır.

Kod Bakım Kolaylığı

SRP’ye uygun olarak tasarlanmış sınıflar, genellikle daha küçük ve daha odaklıdır. Bu durum, bir hata ayıklama veya yeni bir özellik ekleme sırasında ilgili kodu bulmayı ve anlamayı kolaylaştırır. Bir sınıfın tek bir sorumluluğu olduğunda, o sınıfın ne yaptığını ve nasıl çalıştığını kavramak çok daha basittir. Bu da bakım maliyetlerini düşürür ve geliştirme hızını artırır.

Test Edilebilirlik

Tek bir sorumluluğa sahip sınıflar, birim testleri (unit tests) yazmak için idealdir. Her sınıfın sadece belirli bir işlevi yerine getirmesi, o işlevi izole bir şekilde test etmeyi mümkün kılar. Bağımlılıkları az olan ve net sorumluluklara sahip sınıflar, mock veya stub nesnelerle kolayca test edilebilir. Bu da test kapsamını artırır, hataların erken aşamada tespit edilmesini sağlar ve genel yazılım kalitesini yükseltir.

Esneklik ve Genişletilebilirlik

SRP, sistemin farklı bölümlerinin birbirinden bağımsız olarak değişebilmesine olanak tanır. Bir sınıfın tek bir sorumluluğu olduğunda, o sorumlulukla ilgili bir değişiklik yapıldığında, diğer sorumlulukları olan sınıfların etkilenme olasılığı azalır. Bu, yeni özellikler eklerken veya mevcut işlevselliği değiştirirken “yan etki” riskini minimize eder. Böylece, sistem daha esnek hale gelir ve gelecekteki genişletmelere daha kolay adapte olabilir.

SRP’nin “BlindSpot Log” Bağlamında Uygulanması

“BlindSpot Log” gibi bir loglama sisteminde SRP’yi uygulamak, loglama mekanizmasının daha sağlam, esnek ve yönetilebilir olmasını sağlar. Loglama, genellikle birçok farklı sorumluluğu bir araya getirme eğiliminde olan bir alandır.

Loglama Modülünün Sorumlulukları

Tipik bir loglama modülü, aşağıdaki gibi çeşitli sorumlulukları içerebilir:

  • Mesaj Oluşturma: Log mesajının içeriğini ve formatını belirleme.
  • Seviye Kontrolü: Log mesajının önem seviyesine (INFO, WARNING, ERROR vb.) göre filtreleme yapma.
  • Depolama: Log mesajını bir dosyaya, veritabanına, konsola veya uzak bir sunucuya yazma.
  • Formatlama: Log mesajını okunabilir bir formata dönüştürme (JSON, XML, düz metin vb.).
  • Bildirim: Kritik hatalarda e-posta veya SMS gibi bildirimler gönderme.

Eğer tüm bu sorumluluklar tek bir Logger sınıfında toplanırsa, bu sınıfın değişmesi için birden fazla nedeni olur ve SRP ihlal edilmiş olur.

Örnek: SRP’ye Uygun Log Sınıfı Tasarımı

SRP’ye uygun bir loglama sistemi tasarlamak için her bir sorumluluğu ayrı sınıflara ayırmamız gerekir. İşte basit bir örnek:

// SRP İhlali Örneği: Tek Sınıfta Çoklu Sorumluluk
public class BadLogger
{
    public void Log(string message, LogLevel level)
    {
        // 1. Mesajı formatla
        string formattedMessage = $"{DateTime.Now} [{level}] {message}";

        // 2. Mesajı dosyaya yaz
        File.AppendAllText("application.log", formattedMessage + Environment.NewLine);

        // 3. Hata durumunda e-posta gönder
        if (level == LogLevel.Error)
        {
            SendEmail("admin@example.com", "Hata Bildirimi", formattedMessage);
        }
    }

    private void SendEmail(string to, string subject, string body)
    {
        // E-posta gönderme mantığı
        Console.WriteLine($"E-posta gönderildi: {to}, Konu: {subject}, İçerik: {body}");
    }
}

// SRP'ye Uygun Tasarım
public interface ILogFormatter
{
    string Format(string message, LogLevel level);
}

public class DefaultLogFormatter : ILogFormatter
{
    public string Format(string message, LogLevel level)
    {
        return $"{DateTime.Now} [{level}] {message}";
    }
}

public interface ILogWriter
{
    void Write(string formattedMessage);
}

public class FileLogWriter : ILogWriter
{
    private readonly string _filePath;
    public FileLogWriter(string filePath) => _filePath = filePath;

    public void Write(string formattedMessage)
    {
        File.AppendAllText(_filePath, formattedMessage + Environment.NewLine);
    }
}

public class ConsoleLogWriter : ILogWriter
{
    public void Write(string formattedMessage)
    {
        Console.WriteLine(formattedMessage);
    }
}

public interface IErrorNotifier
{
    void Notify(string message);
}

public class EmailNotifier : IErrorNotifier
{
    private readonly string _adminEmail;
    public EmailNotifier(string adminEmail) => _adminEmail = adminEmail;

    public void Notify(string message)
    {
        Console.WriteLine($"E-posta gönderildi: {_adminEmail}, Konu: Hata Bildirimi, İçerik: {message}");
    }
}

public class SSRPLogger // Single Responsibility Principle Logger
{
    private readonly ILogFormatter _formatter;
    private readonly ILogWriter _writer;
    private readonly IErrorNotifier _notifier;

    public SSRPLogger(ILogFormatter formatter, ILogWriter writer, IErrorNotifier notifier)
    {
        _formatter = formatter;
        _writer = writer;
        _notifier = notifier;
    }

    public void Log(string message, LogLevel level)
    {
        string formattedMessage = _formatter.Format(message, level);
        _writer.Write(formattedMessage);

        if (level == LogLevel.Error)
        {
            _notifier.Notify(formattedMessage);
        }
    }
}

public enum LogLevel { Info, Warning, Error }

Bu örnekte, BadLogger sınıfı hem formatlama, hem yazma, hem de bildirim sorumluluklarını üstlenirken, SRP'ye uygun tasarımda bu sorumluluklar ILogFormatter, ILogWriter ve IErrorNotifier arayüzleri ve bunları uygulayan sınıflar aracılığıyla ayrılmıştır. SSRPLogger sınıfının tek sorumluluğu, loglama akışını koordine etmektir.

Loglama Katmanında Sorumluluk Ayrımı

SRP'yi loglama katmanında uygulayarak, her bir loglama bileşeninin kendi işine odaklanmasını sağlarız. Bu ayrım sayesinde:

  • Log formatı değiştiğinde sadece ILogFormatter uygulayan sınıflar etkilenir.
  • Log depolama yöntemi (dosya, veritabanı, bulut) değiştiğinde sadece ILogWriter uygulayan sınıflar etkilenir.
  • Hata bildirim mekanizması değiştiğinde sadece IErrorNotifier uygulayan sınıflar etkilenir.

Bu modüler yapı, "BlindSpot Log" gibi bir sistemin gelecekteki ihtiyaçlara daha kolay adapte olmasını sağlar.

SRP İhlalleri ve Ortak Tuzaklar

SRP'nin faydaları açık olsa da, uygulamada bu prensibi ihlal etmek oldukça yaygındır. Bu ihlaller genellikle "Tanrı Nesneleri" veya çok amaçlı metotlar şeklinde ortaya çıkar.

"Tanrı Nesneleri" (God Objects)

"Tanrı Nesneleri", sistemdeki neredeyse her şeyi bilen ve her şeyi yapan, çok sayıda sorumluluğu üzerinde toplayan sınıflardır. Bu tür sınıflar, genellikle çok büyük olur, anlaşılması zordur ve herhangi bir değişiklik yapıldığında beklenmedik yan etkilere yol açabilir. Bir "Tanrı Nesnesi"nde yapılan küçük bir değişiklik bile, sistemin farklı ve alakasız bölümlerini etkileyebilir, bu da hata ayıklamayı ve bakımı kabusa çevirir.

Çok Amaçlı Metotlar

Sınıflarda olduğu gibi, metotlar da tek bir sorumluluğa sahip olmalıdır. Bir metot, birden fazla farklı görevi yerine getiriyorsa, bu metot da SRP'yi ihlal ediyor demektir. Örneğin, bir metot hem veri doğrulamasını yapıyor, hem veritabanına kaydediyor hem de kullanıcıya bildirim gönderiyorsa, bu metot çok amaçlıdır. Bu tür metotlar, kodun okunabilirliğini azaltır ve test edilmesini zorlaştırır.

Gereksiz Bağımlılıklar

SRP ihlali, genellikle sınıflar arasında gereksiz ve sıkı bağımlılıkların oluşmasına neden olur. Bir sınıf, birden fazla sorumluluğa sahip olduğunda, bu sorumlulukları yerine getirmek için başka birçok sınıfa bağımlı hale gelir. Bu bağımlılıklar, sistemin modülerliğini bozar ve bir bileşendeki değişikliğin domino etkisiyle diğer birçok bileşeni etkilemesine neden olur. Bu durum, özellikle büyük ve karmaşık sistemlerde yönetilmesi zor bir hal alır.

SRP'yi Uygulama Teknikleri ve İpuçları

SRP'yi yazılım tasarımına dahil etmek, bilinçli bir çaba ve bazı pratik teknikler gerektirir. İşte bu prensibi etkili bir şekilde uygulamanıza yardımcı olacak bazı ipuçları:

Sınıf ve Metot Boyutunu Kontrol Etmek

Sınıflarınızın ve metotlarınızın boyutunu küçük tutmaya çalışın. Bir sınıfın yüzlerce satır kodu varsa veya bir metot birden fazla ekranı kaplıyorsa, muhtemelen birden fazla sorumluluğu vardır. Sınıflarınızı ve metotlarınızı daha küçük, daha odaklı parçalara bölmek, SRP'yi uygulamanın ilk adımıdır. Her sınıfın ne iş yaptığını tek bir cümleyle açıklayabiliyorsanız, doğru yoldasınız demektir.

Arayüzlerin Rolü

Arayüzler (interfaces), SRP'yi uygulamanın güçlü bir yoludur. Bir arayüz, bir sınıfın sahip olması gereken belirli bir davranışı veya yeteneği tanımlar. Her arayüzün tek bir sorumluluğu olmalıdır. Sınıflarınızı bu arayüzleri uygulamaya zorlayarak, her sınıfın yalnızca tanımlanmış tek bir sorumluluğu yerine getirmesini sağlayabilirsiniz. Bu, aynı zamanda bağımlılıkları tersine çevirme prensibine (DIP) de katkıda bulunur.

Sorumlulukları Açıkça Tanımlama

Kod yazmaya başlamadan önce, bir sınıfın veya modülün hangi sorumluluklara sahip olacağını net bir şekilde tanımlayın. Bu, tasarım aşamasında SRP ihlallerini tespit etmenize yardımcı olur. Sınıf adlandırmaları da sorumluluğu yansıtmalıdır (örneğin, UserManager yerine UserAuthenticator ve UserRepository gibi daha spesifik isimler). Bir sınıfın birden fazla "ve" bağlacıyla açıklanan bir sorumluluğu varsa (örneğin, "kullanıcıyı doğrular VE veritabanına kaydeder"), bu bir SRP ihlali göstergesidir.

SRP'nin Diğer SOLID Prensipleriyle İlişkisi

SRP, SOLID prensiplerinin temel taşıdır ve diğer prensiplerle sıkı bir ilişki içindedir. Birini doğru uygulamak, genellikle diğerlerinin de daha kolay uygulanmasına yardımcı olur.

Open/Closed Prensibi (OCP) ile Etkileşim

Open/Closed Prensibi (OCP), yazılım varlıklarının (sınıflar, modüller vb.) geliştirmeye açık, ancak değişikliğe kapalı olması gerektiğini söyler. SRP, OCP'yi destekler çünkü her sınıfın tek bir sorumluluğu olduğunda, yeni bir özellik eklemek veya mevcut bir davranışı değiştirmek genellikle mevcut bir sınıfı değiştirmek yerine yeni bir sınıf ekleyerek veya var olanı genişleterek yapılabilir. Bu, mevcut kodu bozma riskini azaltır.

Liskov Yerine Geçme Prensibi (LSP) ve SRP

Liskov Yerine Geçme Prensibi (LSP), türetilmiş sınıfların temel sınıflarının yerine geçebilmesi gerektiğini belirtir. SRP, sınıfların daha odaklı ve tutarlı davranmasını sağlayarak LSP'ye katkıda bulunur. Tek bir sorumluluğa sahip sınıflar, daha az beklenmedik davranış sergiler ve bu da türetilmiş sınıfların temel sınıfların sözleşmesini ihlal etme olasılığını azaltır.

Bağımlılık Tersine Çevirme Prensibi (DIP) ve SRP

Bağımlılık Tersine Çevirme Prensibi (DIP), yüksek seviyeli modüllerin düşük seviyeli modüllere değil, soyutlamalara bağlı olması gerektiğini söyler. SRP, arayüzlerin ve soyutlamaların doğru şekilde kullanılmasını teşvik ederek DIP'yi destekler. Her sorumluluğun ayrı bir arayüzle temsil edilmesi, sınıfların somut uygulamalara değil, soyutlamalara bağımlı olmasını sağlar. Bu, daha esnek ve test edilebilir bir mimari oluşturur.

SRP'nin Avantajları ve Zorlukları

Tek Sorumluluk Prensibi, yazılım geliştirme sürecine önemli avantajlar sağlarken, aynı zamanda bazı uygulama zorluklarını da beraberinde getirebilir.

Geliştirme Sürecine Katkıları

SRP'nin en büyük avantajları arasında kodun daha okunabilir, anlaşılabilir ve yönetilebilir olması yer alır. Bu, geliştirme hızını artırır, hata oranlarını düşürür ve yeni ekip üyelerinin projeye adaptasyonunu kolaylaştırır. Modüler yapı sayesinde, farklı geliştiriciler aynı anda farklı sorumluluklar üzerinde çalışabilir, bu da paralel geliştirmeyi teşvik eder. Ayrıca, sistemin gelecekteki değişikliklere karşı daha dirençli olmasını sağlar, bu da uzun vadede bakım maliyetlerini önemli ölçüde azaltır.

Aşırı Parçalama Riski

SRP'yi uygularken karşılaşılan en yaygın zorluklardan biri, sorumlulukları aşırı derecede parçalama riskidir. Her küçük işlevi ayrı bir sınıfa ayırmak, çok sayıda küçük sınıfın oluşmasına ve kod tabanının gereksiz yere karmaşıklaşmasına yol açabilir. Bu durum, "mikro sınıflar" olarak adlandırılabilir ve bir işlevi tamamlamak için çok sayıda sınıfla etkileşime girmeyi gerektirebilir. Önemli olan, "bir sorumluluk" kavramını doğru bir şekilde yorumlamak ve sistemi gereksiz yere parçalamaktan kaçınmaktır.

Doğru Sorumluluğu Bulmanın Zorluğu

Bir sınıfın tek ve doğru sorumluluğunu belirlemek, özellikle karmaşık iş mantığına sahip sistemlerde zorlayıcı olabilir. "Sorumluluk" kavramı bazen sübjektif olabilir ve farklı geliştiriciler farklı yorumlayabilir. Bu, tasarım tartışmalarını gerektirebilir ve deneyimle geliştirilen bir beceridir. İyi bir tasarım, sorumlulukları net bir şekilde ayırırken, aynı zamanda mantıksal olarak birbiriyle ilişkili işlevleri bir arada tutmayı başarır. Bu dengeyi bulmak, SRP'nin başarılı bir şekilde uygulanmasının anahtarıdır.

Sonuç

Tek Sorumluluk Prensibi (SRP), sadece bir tasarım prensibi olmanın ötesinde, yazılım projelerimizin kalitesini, sürdürülebilirliğini ve yönetilebilirliğini doğrudan etkileyen kritik bir yaklaşımdır. "BlindSpot Log" gibi bir loglama sisteminde SRP'yi uygulamak, loglama mekanizmasını daha esnek, test edilebilir ve gelecekteki değişikliklere daha dirençli hale getirir. Her sınıfın veya modülün tek bir, iyi tanımlanmış bir sorumluluğa sahip olması, kodun okunabilirliğini artırır, hata ayıklamayı kolaylaştırır ve bakım maliyetlerini düşürür. Her ne kadar ilk başta aşırı parçalama gibi görünen bir çaba gerektirse de, SRP'nin uzun vadeli faydaları, bu ilk yatırımın fazlasıyla karşılığını verir. Unutmayın, temiz kod yazmak bir sanattır ve SRP, bu sanatın temel fırça darbelerinden biridir.

SSS (Sıkça Sorulan Sorular)

SRP nedir?

SRP (Single Responsibility Principle - Tek Sorumluluk Prensibi), SOLID prensiplerinin ilkidir ve bir sınıfın veya modülün yalnızca bir sorumluluğu olması gerektiğini, yani değişmesi için yalnızca bir nedeni olması gerektiğini belirtir.

SRP'yi ihlal ettiğimi nasıl anlarım?

Bir sınıfın birden fazla farklı nedenden dolayı değişmesi gerekiyorsa (örneğin, hem veri formatı değiştiğinde hem de veritabanı yapısı değiştiğinde), veya sınıfın adını açıklarken "ve" bağlacını sıkça kullanıyorsanız, muhtemelen SRP'yi ihlal ediyorsunuzdur.

Her sınıfın sadece bir metodu mu olmalı?

Hayır, SRP her sınıfın sadece bir metodu olması gerektiğini söylemez. Bir sınıfın birden fazla metodu olabilir, ancak bu metotların hepsi o sınıfın tek ve ana sorumluluğuyla ilgili olmalıdır. Örneğin, bir UserRepository sınıfının AddUser, GetUserById, UpdateUser gibi metotları olabilir, çünkü hepsi "kullanıcı verilerini yönetme" ana sorumluluğuna hizmet eder.

SRP aşırı parçalamaya yol açar mı?

Yanlış uygulandığında evet, SRP aşırı parçalamaya yol açabilir. Önemli olan, "sorumluluk" kavramını doğru bir şekilde yorumlamak ve her küçük işlevi ayrı bir sınıfa ayırmak yerine, mantıksal olarak birbiriyle ilişkili ve tek bir "değişim ekseni"ne sahip işlevleri bir arada tutmaktır. Bu dengeyi bulmak deneyim gerektirir.

"BlindSpot Log" projesinde SRP'yi uygulamak ne gibi faydalar sağlar?

"BlindSpot Log" projesinde SRP'yi uygulamak, loglama bileşenlerini (formatlama, yazma, bildirim vb.) birbirinden ayırarak her birinin bağımsız olarak geliştirilmesini, test edilmesini ve değiştirilmesini sağlar. Bu, sistemin daha esnek, bakımı kolay ve gelecekteki ihtiyaçlara daha hızlı adapte olabilir olmasını sağlar.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.