Tek Sorumluluk İlkesi. Göründüğü kadar basit değil

Tek Sorumluluk İlkesi. Göründüğü kadar basit değil Tek sorumluluk ilkesi olarak da bilinen tek sorumluluk ilkesi,
diğer adıyla tekdüze değişkenlik ilkesi - anlaması son derece kaygan bir adam ve bir programcı röportajında ​​çok gergin bir soru.

Bu prensiple ilk ciddi tanışmam, ilk yılın başında, genç ve yeşil olanların larvalardan öğrenci - gerçek öğrenci - yapmak için ormana götürülmesiyle gerçekleşti.

Ormanda 8-9 kişilik gruplara ayrıldık ve bir yarışma yaptık - gruptan ilk kişi votkayı bardağa dökerse, ikinci kişi votkayı bardağa dökerse, bir şişe votkayı hangi grup daha hızlı içer, ve üçüncüsünde bir atıştırmalık var. İşlemini tamamlayan birim, grup kuyruğunun sonuna geçer.

Kuyruk boyutunun üçün katı olduğu durum, SRP'nin iyi bir uygulamasıydı.

Tanım 1. Tek sorumluluk.

Tek Sorumluluk İlkesi'nin (SRP) resmi tanımı, her varlığın kendi sorumluluğu ve varoluş nedeni olduğunu ve yalnızca tek bir sorumluluğu olduğunu belirtir.

“İçen” nesnesini düşünün (bahşiş).
SRP ilkesini uygulamak için sorumlulukları üçe ayıracağız:

  • Biri dökülür (Dökme Operasyonu)
  • Bir içki (DrinkUp Operasyonu)
  • Birinde atıştırmalık var (TakeBiteOperasyonu)

Süreçteki katılımcıların her biri, sürecin bir bileşeninden sorumludur, yani tek bir atomik sorumluluğa sahiptir - içmek, dökmek veya atıştırmak.

İçme çukuru da bu operasyonlar için bir cephe görevi görüyor:

сlass Tippler {
    //...
    void Act(){
        _pourOperation.Do() // налить
        _drinkUpOperation.Do() // выпить
        _takeBiteOperation.Do() // закусить
    }
}

Tek Sorumluluk İlkesi. Göründüğü kadar basit değil

Neden?

İnsan programcı maymun adam için kod yazar ve maymun adam dikkatsizdir, aptaldır ve her zaman acelesi vardır. Aynı anda yaklaşık 3-7 terimi tutabilir ve anlayabilir.
Bir sarhoşun durumunda bu terimlerden üçü vardır. Ancak kodu tek sayfayla yazarsak o zaman içinde eller, bardaklar, kavgalar ve bitmek bilmeyen siyaset tartışmaları olur. Ve bunların hepsi tek bir yöntemin bünyesinde olacak. Eminim uygulamalarınızda böyle bir kod görmüşsünüzdür. Ruh için en insani test değil.

Öte yandan maymun adam, gerçek dünyadaki nesneleri kafasında simüle edecek şekilde tasarlanmıştır. Hayalinde bunları bir araya getirebilir, onlardan yeni nesneler bir araya getirebilir ve aynı şekilde parçalarına ayırabilir. Eski model bir araba düşünün. Hayalinizde kapıyı açabilir, kapı kaplamasını sökebilir ve içinde dişlilerin bulunacağı pencere kaldırma mekanizmalarını görebilirsiniz. Ancak makinenin tüm bileşenlerini aynı anda tek bir "listede" göremezsiniz. En azından "maymun adam" bunu yapamaz.

Bu nedenle, insan programcılar karmaşık mekanizmaları daha az karmaşık ve çalışan öğeler kümesine ayrıştırır. Bununla birlikte, farklı şekillerde ayrıştırılabilir: birçok eski arabada hava kanalı kapıya girer ve modern arabalarda, kilit elektroniklerindeki bir arıza, motorun çalışmasını engeller, bu da onarımlar sırasında sorun olabilir.

Ve böylece, SRP, NASIL ayrıştırılacağını, yani ayırma çizgisinin nereye çizileceğini açıklayan bir prensiptir..

“Sorumluluk” ilkesine göre yani belirli nesnelerin görevlerine göre ayrıştırılması gerektiğini söylüyor.

Tek Sorumluluk İlkesi. Göründüğü kadar basit değil

İçmeye ve maymun adamın ayrışma sırasında elde ettiği avantajlara dönelim:

  • Kod her seviyede son derece netleşti
  • Kod aynı anda birden fazla programcı tarafından yazılabilir (her biri ayrı bir öğe yazar)
  • Otomatik test basitleştirilmiştir; öğe ne kadar basitse test edilmesi de o kadar kolay olur
  • Kodun bileşimi belirir - değiştirebilirsiniz DrinkUp Operasyonu bir sarhoşun masanın altına sıvı döktüğü bir operasyona. Veya dökme işlemini, şarap ile suyu veya votka ile birayı karıştırdığınız bir işlemle değiştirin. İş gereksinimlerine bağlı olarak her şeyi yöntem koduna dokunmadan yapabilirsiniz. Tippler.Act.
  • Bu işlemlerden oburluğu katlayabilirsiniz (yalnızca kullanarak) TakeBitOperasyonu), Alkollü (yalnızca DrinkUp Operasyonu doğrudan şişeden) ve diğer birçok iş gereksinimini karşılar.

(Oh, öyle görünüyor ki bu zaten bir OCP prensibi ve ben bu gönderinin sorumluluğunu ihlal ettim)

Ve elbette eksileri:

  • Daha fazla tür yaratmamız gerekecek.
  • Bir ayyaş ilk kez içki içtiğinden birkaç saat sonra içiyor.

Tanım 2. Birleşik değişkenlik.

İzin verin beyler! İçki içme sınıfının da tek bir sorumluluğu vardır; içer! Ve genel olarak "sorumluluk" kelimesi son derece belirsiz bir kavramdır. Birisi insanlığın kaderinden sorumlu, biri de direğe devrilen penguenlerin kaldırılmasından sorumlu.

İçicinin iki uygulamasını ele alalım. Yukarıda bahsedilen ilki üç sınıf içerir: dökün, içirin ve atıştırmalık.

İkincisi “İleri ve Sadece İleri” metodolojisi ile yazılmıştır ve yöntemdeki tüm mantığı içermektedir. Hareket:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
сlass BrutTippler {
   //...
   void Act(){
        // наливаем
    if(!_hand.TryDischarge(from:_bottle, to:_glass, size:_glass.Capacity))
        throw new OverdrunkException();

    // выпиваем
    if(!_hand.TryDrink(from: _glass,  size: _glass.Capacity))
        throw new OverdrunkException();

    //Закусываем
    for(int i = 0; i< 3; i++){
        var food = _foodStore.TakeOrDefault();
        if(food==null)
            throw new FoodIsOverException();

        _hand.TryEat(food);
    }
   }
}

Dışarıdan bir gözlemcinin bakış açısından bu sınıfların her ikisi de tamamen aynı görünüyor ve aynı "içme" sorumluluğunu paylaşıyor.

Bilinç bulanıklığı, konfüzyon!

Daha sonra internete girip SRP'nin başka bir tanımını buluyoruz: Tek Değiştirilebilirlik İlkesi.

SCP şunu belirtiyor: "Bir modülün değişmesi için tek bir nedeni vardır". Yani, “Sorumluluk değişim sebebidir.”

(Görünüşe göre orijinal tanımı bulanlar maymun adamın telepatik yeteneklerine güveniyorlardı)

Artık her şey yerine oturuyor. Ayrı ayrı, dökme, içme ve atıştırma prosedürlerini değiştirebiliriz, ancak içicinin kendisinde yalnızca işlemlerin sırasını ve bileşimini değiştirebiliriz, örneğin içmeden önce atıştırmalıkları hareket ettirerek veya tost okumasını ekleyerek.

“İleri ve Yalnızca İleri” yaklaşımında değiştirilebilecek her şey yalnızca yöntemde değiştirilir. Hareket. Bu, çok az mantık olduğunda ve nadiren değiştiğinde okunabilir ve etkili olabilir, ancak çoğu zaman her biri 500 satırlık korkunç yöntemlerle sonuçlanır ve Rusya'nın NATO'ya katılması için gerekenden daha fazla if ifadesi bulunur.

Tanım 3. Değişikliklerin yerelleştirilmesi.

İçki içenler genellikle neden başka birinin dairesinde uyandıklarını veya cep telefonlarının nerede olduğunu anlamıyorlar. Ayrıntılı günlük kaydı eklemenin zamanı geldi.

Dökme işlemiyle günlüğe kaydetmeye başlayalım:

class PourOperation: IOperation{
    PourOperation(ILogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.Log($"Before pour with {_hand} and {_bottle}");
        //Pour business logic ...
        _log.Log($"After pour with {_hand} and {_bottle}");
    }
}

Onu kapsülleyerek Dökme OperasyonuSorumluluk ve kapsülleme açısından akıllıca davrandık ama şimdi değişkenlik ilkesiyle karıştırıldık. Değişebilen işlemin kendisine ek olarak, günlüğe kaydetmenin kendisi de değiştirilebilir hale gelir. Dökme işlemi için özel bir kaydediciyi ayırmanız ve oluşturmanız gerekecektir:

interface IPourLogger{
    void LogBefore(IHand, IBottle){}
    void LogAfter(IHand, IBottle){}
    void OnError(IHand, IBottle, Exception){}
}

class PourOperation: IOperation{
    PourOperation(IPourLogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.LogBefore(_hand, _bottle);
        try{
             //... business logic
             _log.LogAfter(_hand, _bottle");
        }
        catch(exception e){
            _log.OnError(_hand, _bottle, e)
        }
    }
}

Dikkatli okuyucu bunu fark edecektir. LogAfter, LogBefore и OnHata tek tek de değiştirilebilir ve önceki adımlara benzer şekilde üç sınıf oluşturulacaktır: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

Ve bir içici için üç ameliyat olduğunu hatırlayarak dokuz ağaç kesme dersi alıyoruz. Sonuç olarak, içme çemberinin tamamı 14 (!!!) sınıftan oluşuyor.

Hiperbol? Zorlu! Ayrıştırma bombası taşıyan bir maymun adam, "dökücüyü" bir sürahiye, bir bardağa, dökme operatörlerine, bir su tedarik hizmetine, moleküllerin çarpışmasının fiziksel bir modeline bölecek ve sonraki çeyrekte, bağımlılıkları ortadan kaldırmaya çalışacak. küresel değişkenler. Ve inanın bana durmayacak.

İşte bu noktada birçok kişi SRP'nin pembe krallıklardan gelen peri masalları olduğu sonucuna varıyor ve erişte oynamaya gidiyor...

... Srp'nin üçüncü bir tanımının varlığını hiç öğrenmeden:

“Tek Sorumluluk İlkesi şunu belirtir: değişime benzer şeyler tek bir yerde saklanmalıdır". veya "Birlikte yapılan değişiklikler tek bir yerde tutulmalıdırbaşlıklı bir kılavuz yayınladı

Yani bir işlemin günlüğünü değiştirirsek onu tek bir yerden değiştirmemiz gerekir.

Bu çok önemli bir nokta - çünkü SRP'nin yukarıdaki tüm açıklamaları, türleri ezilirken ezmek gerektiğini, yani nesnenin boyutuna bir "üst sınır" getirdiklerini ve şimdi de bunu söylediğini söylüyordu. zaten bir “alt limit”ten bahsediyoruz. Başka bir deyişle, SRP yalnızca "ezerken ezmeyi" gerektirmez, aynı zamanda aşırıya kaçmamayı da gerektirir - "birbirine kenetlenen şeyleri ezmeyin". Bu, Occam'ın usturası ile maymun adam arasındaki büyük savaş!

Tek Sorumluluk İlkesi. Göründüğü kadar basit değil

Artık içen kişi kendini daha iyi hissetmelidir. IPourLogger günlükçüsünü üç sınıfa ayırmaya gerek olmadığı gerçeğine ek olarak, tüm günlükçüleri tek bir türde de birleştirebiliriz:

class OperationLogger{
    public OperationLogger(string operationName){/*..*/}
    public void LogBefore(object[] args){/*...*/}       
    public void LogAfter(object[] args){/*..*/}
    public void LogError(object[] args, exception e){/*..*/}
}

Ve dördüncü tür bir işlem eklersek, bunun için günlük kaydı zaten hazırdır. Operasyonların kodları da temizdir ve altyapı gürültüsünden arındırılmıştır.

Sonuç olarak, içki sorununu çözmek için 5 sınıfımız var:

  • Dökme işlemi
  • İçme işlemi
  • Sıkışma işlemi
  • Ağaç kesicisi
  • İçki cephesi

Her biri kesin olarak bir işlevden sorumludur ve değişiklik için tek bir nedeni vardır. Değişikliğe benzer tüm kurallar yakınlarda bulunmaktadır.

Gerçek hayattan örnek

Bir zamanlar bir b2b istemcisinin otomatik olarak kaydedilmesi için bir hizmet yazmıştık. Ve 200 satırlık benzer içerik için bir GOD yöntemi ortaya çıktı:

  • 1C'ye gidin ve bir hesap oluşturun
  • Bu hesapla ödeme modülüne gidin ve orada oluşturun
  • Ana sunucuda böyle bir hesaba sahip bir hesabın oluşturulmadığını kontrol edin
  • Yeni bir hesap oluştur
  • Kayıt sonuçlarını ödeme modülüne ve 1c numarasını kayıt sonuçları hizmetine ekleyin
  • Bu tabloya hesap bilgilerini ekleyin
  • Puan hizmetinde bu müşteri için bir puan numarası oluşturun. 1c hesap numaranızı bu hizmete iletin.

Ve bu listede bağlantısı çok kötü olan 10 kadar ticari operasyon daha vardı. Hemen hemen herkesin hesap nesnesine ihtiyacı vardı. Çağrıların yarısında nokta kimliğine ve müşteri adına ihtiyaç duyuldu.

Bir saatlik yeniden düzenlemenin ardından altyapı kodunu ve bir hesapla çalışmanın bazı inceliklerini ayrı yöntemlere/sınıflara ayırmayı başardık. Tanrı yöntemi bunu kolaylaştırdı, ancak çözülmek istemeyen 100 satırlık kod kaldı.

Ancak birkaç gün sonra bu "hafif" yöntemin özünün bir iş algoritması olduğu anlaşıldı. Ve teknik spesifikasyonların orijinal açıklaması oldukça karmaşıktı. Ve SRP'yi ihlal edecek olan, bu yöntemi parçalara ayırma girişimidir, bunun tersi de geçerli değildir.

Şekilcilik.

Sarhoşumuzu rahat bırakmanın zamanı geldi. Gözyaşlarınızı kurulayın - bir gün kesinlikle ona geri döneceğiz. Şimdi bu makaledeki bilgileri resmileştirelim.

Biçimcilik 1. SRP'nin Tanımı

  1. Öğeleri, her biri bir şeyden sorumlu olacak şekilde ayırın.
  2. Sorumluluk “değişim nedeni” anlamına gelir. Yani iş mantığı açısından her unsurun tek bir değişim nedeni vardır.
  3. İş mantığında olası değişiklikler. yerelleştirilmesi gerekir. Eşzamanlı olarak değişen öğelerin yakında olması gerekir.

Biçimcilik 2. Gerekli kendi kendine test kriterleri.

SRP'yi karşılamak için yeterli kriterleri görmedim. Ancak gerekli koşullar var:

1) Kendinize bu sınıfın/yöntemin/modülün/hizmetin ne yaptığını sorun. basit bir tanımla cevaplamalısınız. ( Teşekkür ederim Brightori )

açıklamalar

Ancak bazen basit bir tanım bulmak çok zordur.

2) Bir hatayı düzeltmek veya yeni bir özellik eklemek, minimum sayıda dosya/sınıfı etkiler. İdeal olarak - bir.

açıklamalar

Sorumluluk (bir özellik veya hata için) tek bir dosya/sınıfta kapsandığından, tam olarak nereye bakacağınızı ve neyi düzenleyeceğinizi bilirsiniz. Örneğin: kayıt işlemlerinin çıktısını değiştirme özelliği yalnızca kaydedicinin değiştirilmesini gerektirecektir. Kodun geri kalanını çalıştırmanıza gerek yoktur.

Başka bir örnek, öncekilere benzer şekilde yeni bir kullanıcı arayüzü kontrolü eklemektir. Bu sizi 10 farklı varlık ve 15 farklı dönüştürücü eklemeye zorluyorsa, aşırıya kaçıyorsunuz gibi görünüyor.

3) Birkaç geliştirici projenizin farklı özellikleri üzerinde çalışıyorsa, birleştirme çakışması olasılığı, yani aynı dosyanın/sınıfın aynı anda birkaç geliştirici tarafından değiştirilme olasılığı minimumdur.

açıklamalar

Yeni bir "Masanın altına votka dökün" operasyonunu eklerken, kaydediciyi, içme ve dökme operasyonunu etkilemeniz gerekiyorsa, sorumluluklar çarpık bir şekilde bölünmüş gibi görünüyor. Elbette bu her zaman mümkün olmuyor ama bu rakamı düşürmeye çalışmalıyız.

4) İş mantığı hakkında açıklayıcı bir soru sorulduğunda (bir geliştiriciden veya yöneticiden), kesinlikle bir sınıfa/dosyaya gidersiniz ve yalnızca oradan bilgi alırsınız.

açıklamalar

Özellikler, kurallar veya algoritmalar kompakt bir şekilde, her biri tek bir yerde yazılır ve kod alanı boyunca bayraklarla dağılmaz.

5) Adlandırma açıktır.

açıklamalar

Sınıfımız veya yöntemimiz tek bir şeyden sorumludur ve bu sorumluluk ismine de yansır.

AllManagersManagerService - büyük olasılıkla bir Tanrı sınıfı
LocalPayment - muhtemelen hayır

Biçimcilik 3. Occam-ilk geliştirme metodolojisi.

Tasarımın başlangıcında maymun adam çözülmekte olan problemin tüm inceliklerini bilmez ve hissetmez ve hata yapabilir. Farklı şekillerde hata yapabilirsiniz:

  • Farklı sorumlulukları birleştirerek nesneleri çok büyük hale getirin
  • Tek bir sorumluluğu birçok farklı türe bölerek yeniden çerçeveleme
  • Sorumluluk sınırlarının yanlış tanımlanması

Kuralı hatırlamak önemlidir: "Büyük bir hata yapmak daha iyidir" veya "emin değilseniz, onu bölmeyin." Örneğin, sınıfınız iki sorumluluk içeriyorsa, bu yine de anlaşılabilir bir durumdur ve müşteri kodunda minimum değişiklik yapılarak ikiye bölünebilir. Bağlamın birçok dosyaya yayılması ve istemci kodunda gerekli bağımlılıkların bulunmaması nedeniyle, cam parçalarından bir bardak oluşturmak genellikle daha zordur.

Artık bir gün demenin zamanı geldi

SRP'nin kapsamı OOP ve SOLID ile sınırlı değildir. Yöntemler, işlevler, sınıflar, modüller, mikro hizmetler ve hizmetler için geçerlidir. Hem "figax-figax-and-prod" hem de "roket bilimi" geliştirmeleri için geçerli olup dünyayı her yerde biraz daha iyi hale getirir. Düşünürseniz, bu neredeyse tüm mühendisliğin temel prensibidir. Makine mühendisliği, kontrol sistemleri ve aslında tüm karmaşık sistemler bileşenlerden oluşur ve "yetersiz parçalanma" tasarımcıları esneklikten, "aşırı parçalanma" tasarımcıları verimlilikten, yanlış sınırlar ise onları akıl ve gönül rahatlığından mahrum bırakır.

Tek Sorumluluk İlkesi. Göründüğü kadar basit değil

SRP doğa tarafından icat edilmemiştir ve kesin bilimin bir parçası değildir. Biyolojik ve psikolojik sınırlarımızı aşıyor, maymun adam beynini kullanarak karmaşık sistemleri kontrol etmenin ve geliştirmenin bir yoludur. Bize bir sistemin nasıl ayrıştırılacağını anlatıyor. Orijinal formülasyon makul miktarda telepati gerektiriyordu, ancak umarım bu makale sis perdesinin bir kısmını temizler.

Kaynak: habr.com

Yorum ekle