Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá

Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá Princip jediné odpovědnosti, známý také jako princip jediné odpovědnosti,
alias princip jednotné variability - extrémně kluzký chlap k pochopení a taková nervózní otázka na pohovoru s programátorem.

K mému prvnímu vážnému seznámení s tímto principem došlo na začátku prvního ročníku, kdy se mláďata a zelení odnášeli do lesa, aby z larev udělali studenty – skutečné studenty.

V lese jsme se rozdělili do skupin po 8-9 lidech a soutěžili - která skupina vypije nejrychleji láhev vodky za předpokladu, že první ze skupiny si vodku naleje do sklenice, druhý ji vypije, a třetí má svačinu. Jednotka, která dokončila svou operaci, se přesune na konec fronty skupiny.

Případ, kdy byla velikost fronty násobkem tří, byl dobrou implementací SRP.

Definice 1. Jediná odpovědnost.

Oficiální definice Single Responsibility Principle (SRP) říká, že každý subjekt má svou vlastní odpovědnost a důvod existence a má pouze jednu odpovědnost.

Vezměme si objekt „Drinker“ (Sklápěč).
Abychom implementovali princip SRP, rozdělíme odpovědnosti do tří:

  • Jeden nalévá (PourOperation)
  • Jeden pije (DrinkUpOperation)
  • Jeden má svačinu (TakeBiteOperation)

Každý z účastníků procesu je odpovědný za jednu složku procesu, to znamená, že má jednu atomovou odpovědnost – pít, nalévat nebo svačit.

Pitný otvor je zase fasádou pro tyto operace:

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

Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá

Proč?

Lidský programátor píše kód pro člověka opice a člověk je nepozorný, hloupý a vždy ve spěchu. Dokáže pojmout a porozumět asi 3 - 7 termínům najednou.
V případě opilce jsou tyto pojmy tři. Pokud však kód napíšeme jedním listem, pak bude obsahovat ruce, brýle, rvačky a nekonečné hádky o politice. A to vše bude v těle jedné metody. Jsem si jistý, že jste takový kód ve své praxi viděli. Není to nejhumánnější test na psychiku.

Na druhé straně je lidoop navržen tak, aby simuloval objekty skutečného světa v jeho hlavě. Ve své fantazii je může stlačit k sobě, sestavit z nich nové předměty a stejným způsobem je rozebrat. Představte si starý model auta. Ve své fantazii můžete otevřít dveře, odšroubovat obložení dveří a vidět tam mechanismy zvedání oken, uvnitř kterých budou ozubená kola. Nevidíte ale všechny součásti stroje současně, v jednom „výpisu“. Alespoň „opičí muž“ nemůže.

Lidští programátoři proto složité mechanismy rozkládají na soubor méně složitých a fungujících prvků. Může se však rozkládat různými způsoby: u mnoha starých aut vede vzduchové potrubí do dveří a u moderních aut porucha elektroniky zámku brání nastartování motoru, což může být problém při opravách.

nyní, SRP je princip, který vysvětluje JAK se rozkládat, tedy kde nakreslit dělicí čáru.

Říká, že je třeba se rozkládat podle principu rozdělení „odpovědnosti“, tedy podle úkolů určitých objektů.

Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá

Vraťme se k pití a výhodám, které opičí muž získává během rozkladu:

  • Kód se stal extrémně jasným na všech úrovních
  • Kód může psát několik programátorů najednou (každý píše samostatný prvek)
  • Automatizované testování je zjednodušené – čím jednodušší prvek, tím snazší je testovat
  • Objeví se složení kódu - můžete nahradit DrinkUpOperation k operaci, při které opilec vylévá tekutinu pod stůl. Nebo nahraďte nalévací operaci provozem, ve kterém mícháte víno a vodu nebo vodku a pivo. V závislosti na obchodních požadavcích můžete dělat vše, aniž byste se dotkli kódu metody Tippler.Zákon.
  • Z těchto operací můžete žrout složit (pouze pomocí TakeBitOperation), Alkoholické (pouze pro použití DrinkUpOperation přímo z láhve) a splňují mnoho dalších obchodních požadavků.

(Oh, zdá se, že toto je již princip OCP a porušil jsem odpovědnost tohoto příspěvku)

A samozřejmě nevýhody:

  • Budeme muset vytvořit více typů.
  • Opilec se poprvé napije o pár hodin později, než by to udělal jinak.

Definice 2. Jednotná variabilita.

Dovolte mi, pánové! Pití třída má také jedinou zodpovědnost – pije se! A obecně je slovo „odpovědnost“ extrémně vágní pojem. Někdo je zodpovědný za osud lidstva a někdo je zodpovědný za vychování tučňáků, kteří byli převráceni na pól.

Uvažujme dvě implementace napáječky. První, výše zmíněná, obsahuje tři třídy – nalévat, pít a svačit.

Druhý je napsán pomocí metodologie „Forward and Only Forward“ a obsahuje veškerou logiku metody 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);
    }
   }
}

Obě tyto třídy vypadají z pohledu vnějšího pozorovatele úplně stejně a sdílejí stejnou zodpovědnost „pití“.

Zmatek!

Pak půjdeme online a zjistíme další definici SRP – Princip Single Changeability Principle.

SCP uvádí, že „Modul má jediný důvod ke změně". To znamená: "Odpovědnost je důvodem ke změně."

(Zdá se, že kluci, kteří přišli s původní definicí, byli přesvědčeni o telepatických schopnostech opičího muže)

Nyní vše zapadá na své místo. Samostatně můžeme měnit postupy nalévání, pití a svačiny, ale v napáječce samotné můžeme měnit pouze sled a skladbu operací, například posunutím svačiny před pitím nebo přidáním čtení přípitku.

V přístupu „Forward and Only Forward“ se vše, co lze změnit, mění pouze v metodě Akt. To může být čitelné a účinné, když je v tom málo logiky a málokdy se to mění, ale často to končí strašlivými metodami o 500 řádcích, s více prohlášeními if, než je nutné pro vstup Ruska do NATO.

Definice 3. Lokalizace změn.

Pijáci často nechápou, proč se probudili v cizím bytě, ani kde má mobil. Je čas přidat podrobné protokolování.

Začněme protokolovat proces nalévání:

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

Tím, že to zapouzdříte PourOperation, jednali jsme moudře z hlediska odpovědnosti a zapouzdření, ale nyní jsme zmateni principem variability. Kromě samotné operace, která se může měnit, se mění i samotné protokolování. Budete muset oddělit a vytvořit speciální záznamník pro operaci lití:

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

Pečlivý čtenář si toho všimne LogAfter, LogBefore и OnError lze také změnit jednotlivě a analogicky s předchozími kroky vytvoří tři třídy: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

A když si pamatujeme, že piják má tři operace, dostaneme devět tříd protokolování. Ve výsledku tak celý pitný kroužek tvoří 14 (!!!) tříd.

Hyperbola? Stěží! Opičí muž s rozkladným granátem rozloží „nalévač“ na karafu, sklenici, obsluhu nalévání, vodárenskou službu, fyzikální model srážky molekul a další čtvrtletí se bude snažit rozmotat závislosti bez globální proměnné. A věřte mi, nepřestane.

Právě v tuto chvíli mnozí docházejí k závěru, že SRP jsou pohádky z růžových království, a odcházejí si hrát nudle...

... aniž bychom se kdy dozvěděli o existenci třetí definice Srp:

„Princip jednotné odpovědnosti to říká věci, které jsou podobné změně, by měly být uloženy na jednom místě". nebo "Všechny změny by měly být uloženy na jednom místě"

To znamená, že pokud změníme protokolování operace, musíme ji změnit na jednom místě.

Toto je velmi důležitý bod - protože všechna výše uvedená vysvětlení SRP říkala, že bylo nutné drtit typy, zatímco byly drceny, to znamená, že uvalily „horní limit“ na velikost objektu a nyní již mluvíme o „dolní hranici“. Jinými slovy, SRP nejen vyžaduje „drcení při drcení“, ale také to nepřehánět – „nedrtit do sebe zapadající věci“. Toto je velká bitva mezi Occamovou břitvou a lidoopem!

Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá

Nyní by se měl piják cítit lépe. Kromě toho, že není potřeba rozdělovat IPourLogger logger do tří tříd, můžeme také všechny loggery sloučit do jednoho 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 pokud k tomu přidáme čtvrtý typ operace, pak je protokolování pro něj již připraveno. A kód samotných operací je čistý a bez hluku infrastruktury.

V důsledku toho máme 5 tříd pro řešení problému s pitím:

  • Operace nalévání
  • Pitný provoz
  • Operace rušení
  • Logger
  • Fasáda napáječky

Každý z nich je zodpovědný striktně za jednu funkcionalitu a má jeden důvod ke změně. Všechna pravidla podobná změně se nacházejí poblíž.

Příklad ze skutečného života

Kdysi jsme napsali službu pro automatickou registraci b2b klienta. A metoda GOD se objevila pro 200 řádků podobného obsahu:

  • Přejděte na 1C a vytvořte si účet
  • S tímto účtem přejděte do platebního modulu a vytvořte jej tam
  • Zkontrolujte, zda na hlavním serveru nebyl vytvořen účet s takovým účtem
  • Vytvořit nový účet
  • Přidejte výsledky registrace v platebním modulu a číslo 1c do služby výsledků registrace
  • Přidejte do této tabulky informace o účtu
  • Vytvořte číslo bodu pro tohoto klienta v bodové službě. Předejte této službě číslo svého účtu 1c.

A na tomto seznamu bylo asi 10 dalších obchodních operací s hroznou konektivitou. Téměř každý potřeboval objekt účtu. U poloviny hovorů bylo potřeba ID bodu a jméno klienta.

Po hodině refaktoringu jsme byli schopni rozdělit kód infrastruktury a některé nuance práce s účtem do samostatných metod/tříd. Metoda God to usnadnila, ale zbývalo 100 řádků kódu, které se prostě nechtěly rozmotat.

Teprve po několika dnech se ukázalo, že podstatou této „odlehčené“ metody je obchodní algoritmus. A že původní popis technických specifikací byl poměrně složitý. A právě pokus rozbít tuto metodu na kousky poruší SRP, a ne naopak.

Formalismus.

Je čas nechat našeho opilce na pokoji. Osušte si slzy – určitě se k tomu ještě někdy vrátíme. Nyní formalizujme znalosti z tohoto článku.

Formalismus 1. Definice SRP

  1. Oddělte prvky tak, aby každý z nich odpovídal za jednu věc.
  2. Odpovědnost znamená „důvod ke změně“. To znamená, že každý prvek má pouze jeden důvod pro změnu, z hlediska obchodní logiky.
  3. Možné změny obchodní logiky. musí být lokalizován. Prvky, které se mění synchronně, musí být poblíž.

Formalismus 2. Nezbytná kritéria autotestu.

Neviděl jsem dostatečná kritéria pro splnění SRP. Ale jsou nutné podmínky:

1) Zeptejte se sami sebe, co tato třída/metoda/modul/služba dělá. musíte na to odpovědět jednoduchou definicí. ( Děkuji Brightori )

vysvětlení

Někdy je však velmi obtížné najít jednoduchou definici

2) Oprava chyby nebo přidání nové funkce ovlivňuje minimální počet souborů/tříd. Ideálně - jeden.

vysvětlení

Protože odpovědnost (za funkci nebo chybu) je zapouzdřena v jednom souboru/třídě, víte přesně, kde hledat a co upravit. Například: funkce změny výstupu protokolovacích operací bude vyžadovat změnu pouze zapisovače. Není třeba procházet zbytek kódu.

Dalším příkladem je přidání nového ovládacího prvku uživatelského rozhraní, podobného těm předchozím. Pokud vás to nutí přidat 10 různých entit a 15 různých převodníků, vypadá to, že to přeháníte.

3) Pokud několik vývojářů pracuje na různých funkcích vašeho projektu, pak je pravděpodobnost konfliktu sloučení, tedy pravděpodobnost, že stejný soubor/třída bude změněn několika vývojáři současně, minimální.

vysvětlení

Pokud při přidávání nové operace „Nalít vodku pod stůl“ potřebujete ovlivnit logger, provoz pití a nalévání, pak to vypadá, že jsou povinnosti rozděleny křivě. Samozřejmě to není vždy možné, ale měli bychom se pokusit toto číslo snížit.

4) Když se zeptáte na upřesňující otázku o obchodní logice (od vývojáře nebo manažera), jdete striktně do jedné třídy/souboru a přijímáte informace pouze odtud.

vysvětlení

Funkce, pravidla nebo algoritmy jsou napsány kompaktně, každý na jednom místě, a nejsou rozptýleny s příznaky po celém kódovém prostoru.

5) Pojmenování je jasné.

vysvětlení

Naše třída nebo metoda je zodpovědná za jednu věc a odpovědnost se odráží v jejím názvu

AllManagersManagerService - s největší pravděpodobností třída Boha
LocalPayment – ​​pravděpodobně ne

Formalismus 3. Metodologie vývoje Occam-first.

Na začátku designu opičí muž nezná a necítí všechny jemnosti řešeného problému a může udělat chybu. Chyby můžete dělat různými způsoby:

  • Udělejte objekty příliš velké sloučením různých odpovědností
  • Přerámování rozdělením jedné odpovědnosti do mnoha různých typů
  • Špatně definovat hranice odpovědnosti

Je důležité si zapamatovat pravidlo: „Je lepší udělat velkou chybu“ nebo „pokud si nejste jisti, nerozebírejte to“. Pokud například vaše třída obsahuje dvě odpovědnosti, pak je to stále pochopitelné a lze ji rozdělit na dvě s minimálními změnami v kódu klienta. Sestavení sklenice ze skleněných úlomků je obvykle obtížnější kvůli kontextu, který je rozprostřen do několika souborů a nedostatku potřebných závislostí v klientském kódu.

Je čas nazvat to dnem

Rozsah SRP není omezen na OOP a SOLID. Vztahuje se na metody, funkce, třídy, moduly, mikroslužby a služby. Týká se jak vývoje „figax-figax-and-prod“, tak „rocket-science“, díky čemuž je svět všude o něco lepší. Když se nad tím zamyslíte, je to téměř základní princip veškerého inženýrství. Strojírenství, řídicí systémy a vlastně všechny složité systémy jsou stavěny z komponent a „podfragmentace“ připravuje konstruktéry o flexibilitu, „nadměrná fragmentace“ připravuje konstruktéry o efektivitu a nesprávné hranice je zbavují rozumu a duševního klidu.

Princip jednotné odpovědnosti. Není to tak jednoduché, jak se zdá

SRP nevynalezla příroda a není součástí exaktní vědy. Vymyká se našim biologickým a psychologickým omezením. Je to jen způsob, jak ovládat a rozvíjet složité systémy pomocí mozku lidoopů. Říká nám, jak rozložit systém. Původní formulace vyžadovala pořádnou dávku telepatie, ale doufám, že tento článek vymaže část kouřové clony.

Zdroj: www.habr.com

Přidat komentář