Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá

Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá princíp jedinej zodpovednosti, známy aj ako princíp jedinej zodpovednosti,
alias princíp jednotnej variability - extrémne klzký chlap na pochopenie a taká nervózna otázka na pohovore s programátorom.

Moje prvé vážnejšie oboznámenie sa s týmto princípom prebehlo začiatkom prvého ročníka, keď sa mláďatá a zelené brali do lesa, aby z lariev urobili žiakov – skutočných žiakov.

V lese sme sa rozdelili do skupín po 8-9 ľudí a súťažili sme, ktorá skupina vypije fľašu vodky najrýchlejšie za predpokladu, že prvý zo skupiny si vodku naleje do pohára, druhý vypije, a tretí má občerstvenie. Jednotka, ktorá dokončila svoju operáciu, sa presunie na koniec poradia skupiny.

Prípad, keď bola veľkosť frontu násobkom troch, bol dobrou implementáciou SRP.

Definícia 1. Jediná zodpovednosť.

Oficiálna definícia Single Responsibility Principle (SRP) hovorí, že každý subjekt má svoju vlastnú zodpovednosť a dôvod existencie a má len jednu zodpovednosť.

Zvážte objekt „Drinker“ (Tippler).
Na implementáciu princípu SRP rozdelíme zodpovednosti do troch:

  • Jeden leje (PourOperation)
  • Jeden pije (DrinkUpOperation)
  • Jeden má občerstvenie (TakeBiteOperation)

Každý z účastníkov procesu je zodpovedný za jednu zložku procesu, to znamená, že má jednu atómovú zodpovednosť - piť, nalievať alebo jesť.

Pitný otvor je zasa fasádou pre tieto prevádzky:

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

Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá

Prečo?

Ľudský programátor píše kód pre opičieho človeka a opičí človek je nepozorný, hlúpy a vždy sa ponáhľa. Dokáže udržať a pochopiť asi 3 - 7 výrazov naraz.
V prípade opilca sú tieto pojmy tri. Ak však kód napíšeme jedným hárkom, potom bude obsahovať ruky, okuliare, bitky a nekonečné hádky o politike. A to všetko bude v tele jednej metódy. Som si istý, že ste vo svojej praxi videli takýto kód. Nie najhumánnejší test na psychiku.

Na druhej strane ľudoop je navrhnutý tak, aby simuloval objekty skutočného sveta v jeho hlave. Vo svojej fantázii ich môže stláčať, zostavovať z nich nové predmety a rovnakým spôsobom ich rozoberať. Predstavte si starý model auta. Vo svojej fantázii môžete otvoriť dvere, odskrutkovať obloženie dverí a vidieť tam mechanizmy zdvíhania okien, vo vnútri ktorých budú ozubené kolesá. Ale nemôžete vidieť všetky komponenty stroja naraz, v jednom "výpise". Aspoň „opičí muž“ nemôže.

Preto ľudskí programátori rozkladajú zložité mechanizmy na súbor menej zložitých a fungujúcich prvkov. Dá sa však rozložiť rôznymi spôsobmi: v mnohých starých autách vedie vzduchové potrubie do dverí a v moderných autách porucha elektroniky zámku bráni naštartovaniu motora, čo môže byť problém pri opravách.

teraz, SRP je princíp, ktorý vysvetľuje AKO sa rozkladať, teda kde nakresliť deliacu čiaru.

Hovorí, že je potrebné rozkladať sa podľa princípu rozdelenia „zodpovednosti“, teda podľa úloh určitých objektov.

Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá

Vráťme sa k pitiu a výhodám, ktoré opičí muž získava počas rozkladu:

  • Kód sa stal mimoriadne jasným na každej úrovni
  • Kód môže písať niekoľko programátorov naraz (každý píše samostatný prvok)
  • Automatizované testovanie je zjednodušené – čím je prvok jednoduchší, tým je testovanie jednoduchšie
  • Objaví sa zloženie kódu - môžete nahradiť DrinkUpOperation na operáciu, pri ktorej opilec vyleje tekutinu pod stôl. Alebo nahraďte operáciu nalievania prevádzkou, v ktorej zmiešate víno a vodu alebo vodku a pivo. V závislosti od obchodných požiadaviek môžete robiť všetko bez toho, aby ste sa dotkli kódu metódy Tippler.Zákon.
  • Z týchto operácií môžete poskladať žrúta (iba pomocou TakeBitOperation), Alkohol (iba na použitie DrinkUpOperation priamo z fľaše) a spĺňajú mnohé ďalšie obchodné požiadavky.

(Och, zdá sa, že toto je už princíp OCP a porušil som zodpovednosť tohto príspevku)

A samozrejme, nevýhody:

  • Budeme musieť vytvoriť viac typov.
  • Opilec sa prvýkrát napije o pár hodín neskôr, ako by to mal inak.

Definícia 2. Jednotná variabilita.

Dovoľte mi, páni! Trieda pitia má tiež jedinú zodpovednosť – pije sa! A vo všeobecnosti je slovo „zodpovednosť“ mimoriadne vágnym pojmom. Niekto je zodpovedný za osud ľudstva a niekto je zodpovedný za chov tučniakov, ktoré boli prevrátené na pól.

Uvažujme o dvoch implementáciách napájačky. Prvá, spomenutá vyššie, obsahuje tri triedy – nalievať, piť a desiatovať.

Druhá je napísaná pomocou metodiky „Forward and Only Forward“ a obsahuje všetku logiku metódy akt:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
с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);
    }
   }
}

Obe tieto triedy z pohľadu vonkajšieho pozorovateľa vyzerajú úplne rovnako a zdieľajú rovnakú zodpovednosť za „pitie“.

Zmätok!

Potom ideme online a zistíme ďalšiu definíciu SRP – princíp jednotnej výmeny.

SCP uvádza, že „Modul má jeden a jediný dôvod na zmenu". To znamená: "Zodpovednosť je dôvodom na zmenu."

(Zdá sa, že chlapci, ktorí prišli s pôvodnou definíciou, boli presvedčení o telepatických schopnostiach opičieho muža)

Teraz všetko padne na svoje miesto. Samostatne môžeme meniť postupy nalievania, pitia a maškrtenia, ale v samotnom napájadle môžeme meniť len postupnosť a skladbu operácií, napríklad posunutím občerstvenia pred pitím alebo pridaním čítania prípitku.

V prístupe „Forward and Only Forward“ sa všetko, čo sa dá zmeniť, zmení iba v metóde akt. To môže byť čitateľné a efektívne, keď je v tom málo logiky a málokedy sa to mení, ale často to končí hroznými metódami, každý má 500 riadkov, s väčším počtom if-výrokov, ako je potrebné na vstup Ruska do NATO.

Definícia 3. Lokalizácia zmien.

Pijani často nechápu, prečo sa zobudili v cudzom byte, ani kde má mobil. Je čas pridať podrobné protokolovanie.

Začnime protokolovať proces nalievania:

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}");
    }
}

Jeho zapuzdrením PourOperation, konali sme múdro z pohľadu zodpovednosti a zapuzdrenia, no teraz sme zmätení s princípom variability. Okrem samotnej prevádzky, ktorá sa môže meniť, sa mení aj samotné protokolovanie. Budete musieť oddeliť a vytvoriť špeciálny záznamník pre operáciu nalievania:

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)
        }
    }
}

Pozorný čitateľ si to všimne LogAfter, LogBefore и OnError možno zmeniť aj jednotlivo a analogicky s predchádzajúcimi krokmi vytvorí tri triedy: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

A keď si uvedomíme, že pijan má tri operácie, dostaneme deväť tried ťažby dreva. Výsledkom je, že celý pitný krúžok pozostáva zo 14 (!!!) tried.

Hyperbola? Sotva! Opičí muž s rozkladným granátom rozdelí „nalievačku“ na karafu, pohár, operátorov nalievania, vodárenskú službu, fyzikálny model zrážky molekúl a ďalšiu štvrtinu sa bude snažiť rozmotať závislosti bez globálne premenné. A verte mi, neprestane.

V tomto bode mnohí dospejú k záveru, že SRP sú rozprávky z ružových kráľovstiev a odchádzajú hrať rezance...

... bez toho, aby sme sa dozvedeli o existencii tretej definície Srp:

„Princíp jednotnej zodpovednosti to uvádza veci, ktoré sú podobné zmene, by mali byť uložené na jednom mieste". alebo "To, čo sa zmení spolu, by sa malo uchovávať na jednom mieste"

To znamená, že ak zmeníme protokolovanie operácie, musíme ju zmeniť na jednom mieste.

Toto je veľmi dôležitý bod - keďže všetky vysvetlenia SRP, ktoré boli uvedené vyššie, hovorili, že bolo potrebné drviť typy počas ich drvenia, to znamená, že stanovili „horný limit“ na veľkosť objektu a teraz už hovoríme o „dolnom limite“ . Inými slovami, SRP si vyžaduje nielen „drviť pri drvení“, ale tiež to nepreháňať – „nedrviť do seba zapadajúce veci“. Toto je veľký boj medzi Occamovou britvou a ľudoopom!

Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá

Teraz by sa mal pijan cítiť lepšie. Okrem toho, že nie je potrebné deliť logger IPourLogger do troch tried, môžeme všetky loggery spojiť do jedného typu:

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

A ak k tomu pridáme štvrtý typ operácie, tak protokolovanie pre ňu je už pripravené. A kód samotných operácií je čistý a bez hluku z infraštruktúry.

V dôsledku toho máme 5 tried na riešenie problému s pitím:

  • Operácia nalievania
  • Pitná prevádzka
  • Operácia rušenia
  • Logger
  • Fasáda napájačky

Každý z nich je zodpovedný striktne za jednu funkčnosť a má jeden dôvod na zmenu. Všetky pravidlá podobné zmene sa nachádzajú v blízkosti.

Príklad zo skutočného života

Raz sme napísali službu na automatickú registráciu b2b klienta. A metóda GOD sa objavila pre 200 riadkov podobného obsahu:

  • Prejdite na 1C a vytvorte si účet
  • S týmto účtom prejdite do platobného modulu a vytvorte ho tam
  • Skontrolujte, či účet s takýmto účtom nebol vytvorený na hlavnom serveri
  • Vytvorte si nový účet
  • Pridajte výsledky registrácie v platobnom module a číslo 1c do služby výsledkov registrácie
  • Do tejto tabuľky pridajte informácie o účte
  • Vytvorte číslo bodu pre tohto klienta v službe bodov. Tejto službe odovzdajte číslo svojho účtu 1c.

A na tomto zozname bolo asi 10 ďalších obchodných operácií s hroznou konektivitou. Objekt účtu potreboval takmer každý. V polovici hovorov bolo potrebné ID bodu a meno klienta.

Po hodine refaktorovania sme dokázali rozdeliť kód infraštruktúry a niektoré nuansy práce s účtom do samostatných metód/tried. Božia metóda to uľahčila, ale zostalo 100 riadkov kódu, ktorý sa jednoducho nechcel rozmotať.

Až po niekoľkých dňoch sa ukázalo, že podstatou tejto „ľahkej“ metódy je obchodný algoritmus. A že pôvodný popis technických špecifikácií bol dosť zložitý. A práve pokus rozdeliť túto metódu na kúsky poruší SRP, a nie naopak.

formalizmus.

Je čas nechať nášho opilca na pokoji. Osušte si slzy – určite sa k tomu ešte niekedy vrátime. Teraz formalizujme poznatky z tohto článku.

Formalizmus 1. Definícia SRP

  1. Oddeľte prvky tak, aby každý z nich bol zodpovedný za jednu vec.
  2. Zodpovednosť znamená „dôvod na zmenu“. To znamená, že každý prvok má len jeden dôvod na zmenu, z hľadiska obchodnej logiky.
  3. Potenciálne zmeny obchodnej logiky. musia byť lokalizované. Prvky, ktoré sa menia synchrónne, musia byť v blízkosti.

Formalizmus 2. Nevyhnutné kritériá autotestu.

Nevidel som dostatočné kritériá na splnenie SRP. Ale sú potrebné podmienky:

1) Opýtajte sa sami seba, čo robí táto trieda/metóda/modul/služba. musíte na to odpovedať jednoduchou definíciou. ( Ďakujem Brightori )

vysvetlenia

Niekedy je však veľmi ťažké nájsť jednoduchú definíciu

2) Oprava chyby alebo pridanie novej funkcie ovplyvňuje minimálny počet súborov/tried. V ideálnom prípade - jeden.

vysvetlenia

Keďže zodpovednosť (za funkciu alebo chybu) je zapuzdrená v jednom súbore/triede, presne viete, kde hľadať a čo upraviť. Napríklad: funkcia zmeny výstupu operácií protokolovania bude vyžadovať zmenu iba zapisovača. Nie je potrebné prechádzať zvyšok kódu.

Ďalším príkladom je pridanie nového ovládacieho prvku používateľského rozhrania, podobného tým predchádzajúcim. Ak vás to núti pridať 10 rôznych entít a 15 rôznych konvertorov, vyzerá to, že to preháňate.

3) Ak viacerí vývojári pracujú na rôznych funkciách vášho projektu, potom je pravdepodobnosť konfliktu zlučovania, teda pravdepodobnosť, že rovnaký súbor/trieda bude zmenená niekoľkými vývojármi súčasne, minimálna.

vysvetlenia

Ak pri pridávaní novej operácie „Nalejte vodku pod stôl“ potrebujete ovplyvniť zapisovač, chod pitia a nalievania, tak to vyzerá, že povinnosti sú rozdelené krivo. Samozrejme, nie je to vždy možné, ale mali by sme sa pokúsiť toto číslo znížiť.

4) Keď dostanete objasňujúcu otázku o obchodnej logike (od vývojára alebo manažéra), idete striktne do jednej triedy/súboru a dostávate informácie iba odtiaľ.

vysvetlenia

Funkcie, pravidlá alebo algoritmy sú napísané kompaktne, každý na jednom mieste a nie sú rozptýlené príznakmi po celom kódovom priestore.

5) Pomenovanie je jasné.

vysvetlenia

Naša trieda alebo metóda je zodpovedná za jednu vec a zodpovednosť sa odráža v jej názve

AllManagersManagerService - s najväčšou pravdepodobnosťou trieda Boha
LocalPayment - pravdepodobne nie

Formalizmus 3. Occam-first rozvojová metodológia.

Na začiatku dizajnu opičí muž nepozná a necíti všetky jemnosti riešeného problému a môže urobiť chybu. Chyby môžete robiť rôznymi spôsobmi:

  • Urobte objekty príliš veľké zlúčením rôznych zodpovedností
  • Prerámcovanie rozdelením jednej zodpovednosti do mnohých rôznych typov
  • Nesprávne vymedzenie hraníc zodpovednosti

Je dôležité si zapamätať pravidlo: „je lepšie urobiť veľkú chybu“ alebo „ak si nie si istý, nerozdeľuj to“. Ak napríklad vaša trieda obsahuje dve zodpovednosti, potom je to stále pochopiteľné a dá sa rozdeliť na dve s minimálnymi zmenami v kóde klienta. Zostavenie pohára zo sklenených črepov je zvyčajne náročnejšie z dôvodu rozmiestnenia kontextu vo viacerých súboroch a nedostatku potrebných závislostí v kóde klienta.

Je čas nazvať to dňom

Rozsah SRP nie je obmedzený na OOP a SOLID. Vzťahuje sa na metódy, funkcie, triedy, moduly, mikroslužby a služby. Vzťahuje sa na vývoj „figax-figax-and-prod“ aj „rocket-science“, vďaka čomu je svet všade o niečo lepší. Ak sa nad tým zamyslíte, toto je takmer základný princíp celého inžinierstva. Strojárstvo, riadiace systémy a vlastne všetky zložité systémy sú postavené z komponentov a „podfragmentácia“ pripravuje dizajnérov o flexibilitu, „overfragmentácia“ oberá dizajnérov o efektívnosť a nesprávne hranice ich oberajú o rozum a pokoj.

Princíp jednotnej zodpovednosti. Nie je to také jednoduché, ako sa zdá

SRP nevymyslela príroda a nie je súčasťou exaktnej vedy. Vymyká sa našim biologickým a psychologickým obmedzeniam. Je to len spôsob, ako ovládať a rozvíjať zložité systémy pomocou mozgu ľudoopov. Hovorí nám, ako rozložiť systém. Pôvodná formulácia si vyžadovala poriadnu dávku telepatie, ale dúfam, že tento článok vymaže časť dymovej clony.

Zdroj: hab.com

Pridať komentár