Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi

Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi Načelo enotne odgovornosti, znano tudi kot načelo enotne odgovornosti,
aka načelo enotne variabilnosti - izjemno spolzka oseba za razumevanje in tako nervozno vprašanje na razgovoru za programerja.

Moje prvo resnejše seznanjanje s tem principom se je zgodilo na začetku prvega letnika, ko so mlade in zelene odpeljali v gozd, da so iz ličink delali učence – prave učence.

V gozdu smo se razdelili v skupine po 8-9 ljudi in tekmovali - katera skupina bo najhitreje popila steklenico vodke, s tem da prvi iz skupine natoči vodko v kozarec, drugi jo popije, tretji pa ima malico. Enota, ki je zaključila svojo operacijo, se premakne na konec čakalne vrste skupine.

Primer, ko je bila velikost čakalne vrste večkratnik tri, je bila dobra izvedba SRP.

Opredelitev 1. Enotna odgovornost.

Uradna definicija načela enotne odgovornosti (SRP) pravi, da ima vsak subjekt svojo odgovornost in razlog za obstoj ter ima samo eno odgovornost.

Razmislite o predmetu "Pivec" (Tipler).
Za izvajanje načela SRP bomo odgovornosti razdelili na tri:

  • Eden nalije (PourOperation)
  • ena pijača (DrinkUpOperation)
  • Eden ima malico (TakeBiteOperation)

Vsak od udeležencev v procesu je odgovoren za eno komponento procesa, torej ima eno atomsko odgovornost - piti, natočiti ali prigrizniti.

Luknja za pitje pa je fasada za te operacije:

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

Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi

Zakaj?

Človeški programer piše kodo za opičjega človeka, opičji človek pa je nepazljiv, neumen in vedno se mudi. Lahko zadrži in razume približno 3-7 izrazov hkrati.
Pri pijancu so ti pojmi trije. Če pa kodeks napišemo z enim listom, potem bodo na njem roke, očala, prepiri in neskončni prepiri o politiki. In vse to bo v telesu ene metode. Prepričan sem, da ste v svoji praksi že videli takšno kodo. Ni najbolj human test za psiho.

Po drugi strani pa je opičji človek zasnovan tako, da v svoji glavi simulira predmete iz resničnega sveta. V svoji domišljiji jih lahko potiska skupaj, iz njih sestavlja nove predmete in jih na enak način razstavlja. Predstavljajte si star model avtomobila. V svoji domišljiji lahko odprete vrata, odvijete oblogo vrat in tam vidite mehanizme za dviganje oken, znotraj katerih bodo zobniki. Toda ne morete videti vseh komponent stroja hkrati, v enem "seznamu". Vsaj »opičji človek« ne more.

Zato človeški programerji kompleksne mehanizme razgradijo v niz manj kompleksnih in delujočih elementov. Lahko pa se razgrajuje na različne načine: pri marsikaterem starem avtu gre zračnik v vrata, pri modernih avtomobilih pa okvara elektronike ključavnice onemogoča zagon motorja, kar je lahko težava pri popravilih.

Zdaj, SRP je princip, ki pojasnjuje, KAKO razgraditi, torej kje potegniti ločnico.

Pravi, da je treba razgraditi po načelu delitve »odgovornosti«, torej po nalogah določenih objektov.

Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi

Vrnimo se k pitju in prednostim, ki jih dobi opičji človek med razgradnjo:

  • Koda je postala izjemno jasna na vseh ravneh
  • Kodo lahko piše več programerjev hkrati (vsak piše svoj element)
  • Avtomatsko testiranje je poenostavljeno – enostavnejši kot je element, lažje ga je testirati
  • Pojavi se sestavljivost kode - lahko zamenjate DrinkUpOperation na operacijo, pri kateri pijanec zlije tekočino pod mizo. Ali zamenjajte točenje s postopkom, pri katerem zmešate vino in vodo ali vodko in pivo. Glede na poslovne zahteve lahko naredite vse, ne da bi se dotaknili kode metode Tippler.Act.
  • Iz teh operacij lahko zložite požrešnika (z uporabo samo TakeBitOperation), Alkoholik (samo pri uporabi DrinkUpOperation neposredno iz steklenice) in izpolnjujejo številne druge poslovne zahteve.

(Oh, zdi se, da je to že načelo OCP in sem prekršil odgovornost te objave)

In seveda slabosti:

  • Ustvariti bomo morali več vrst.
  • Pijanec prvič popije nekaj ur kasneje, kot bi sicer.

Definicija 2. Enotna variabilnost.

Dovolite, gospodje! Tudi razred pitja ima eno samo odgovornost - pije! In na splošno je beseda "odgovornost" zelo nejasen koncept. Nekdo je odgovoren za usodo človeštva, nekdo pa je odgovoren za vzgojo pingvinov, ki so bili prevrnjeni na pol.

Oglejmo si dve izvedbi pitnika. Prvi, omenjen zgoraj, vsebuje tri razrede - točenje, pijačo in prigrizek.

Drugi je napisan skozi metodologijo »Naprej in samo naprej« in vsebuje vso logiko v metodi Zakon:

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

Oba razreda z vidika zunanjega opazovalca izgledata povsem enako in si delita enako odgovornost "pitja".

Zmeda!

Nato gremo na splet in poiščemo še eno definicijo SRP – načelo ene same spremenljivosti.

SCP navaja, da "Modul ima en in samo en razlog za spremembo". Se pravi: "Odgovornost je razlog za spremembo."

(Zdi se, da so bili fantje, ki so se domislili prvotne definicije, prepričani v telepatske sposobnosti človeka opice)

Zdaj se vse postavi na svoje mesto. Ločeno lahko spreminjamo postopke točenja, pitja in prigrizkov, v samem pitniku pa lahko spreminjamo samo zaporedje in sestavo operacij, na primer s premikanjem prigrizka pred pitjem ali dodajanjem branja zdravice.

Pri pristopu »Naprej in samo naprej« se vse, kar je mogoče spremeniti, spremeni samo v metodi Zakon. To je lahko berljivo in učinkovito, ko je malo logike in se le redko spremeni, vendar se pogosto konča z grozljivimi metodami po 500 vrstic, z več izjavami če, kot je potrebno, da se Rusija pridruži Natu.

Definicija 3. Lokalizacija sprememb.

Pivci pogosto ne razumejo, zakaj so se zbudili v stanovanju nekoga drugega ali kje je njihov mobilni telefon. Čas je, da dodate podrobno beleženje.

Začnimo beleženje s postopkom vlivanja:

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

Z inkapsulacijo v PourOperation, z vidika odgovornosti in enkapsulacije smo ravnali modro, zdaj pa nas zamenjuje princip variabilnosti. Poleg samega delovanja, ki se lahko spremeni, postane spremenljivo tudi samo beleženje. Za operacijo izlivanja boste morali ločiti in ustvariti poseben zapisovalnik:

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

Natančen bralec bo to opazil LogAfter, LogBefore и OnError lahko spremenite tudi posamično in po analogiji s prejšnjimi koraki ustvarite tri razrede: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

In ob upoštevanju, da so za pivca tri operacije, dobimo devet razredov sečnje. Posledično je celoten krog pitja sestavljen iz 14 (!!!) razredov.

Hiperbola? Komaj! Opičji mož z razkrojno granato bo »točilnik« razdelil na dekanter, kozarec, operaterje točenja, vodovod, fizikalni model trka molekul in naslednje četrtletje poskušal razvozlati odvisnosti brez globalne spremenljivke. In verjemite mi, ne bo se ustavil.

Na tej točki mnogi pridejo do zaključka, da so SRP pravljice iz rožnatih kraljestev, in se odpravijo igrat rezance...

... ne da bi sploh izvedeli za obstoj tretje definicije Srp:

"Načelo enotne odgovornosti to določa stvari, ki so podobne drobižu, naj bodo shranjene na enem mestu". ali "Kar se skupaj spremeni, naj bo na enem mestu"

To pomeni, da če spremenimo beleženje operacije, ga moramo spremeniti na enem mestu.

To je zelo pomembna točka - saj so vse zgornje razlage SRP povedale, da je treba zdrobiti vrste, medtem ko so bile zdrobljene, to je, da so določili "zgornjo mejo" velikosti predmeta, zdaj pa že govorimo o »spodnji meji« . Z drugimi besedami, SRP ne zahteva samo "drobljenja med drobljenjem", ampak tudi, da ne pretiravate - "ne drobite prepletenih stvari". To je velika bitka med Occamovo britvico in človekom opico!

Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi

Zdaj bi se moral pivec počutiti bolje. Poleg tega, da zapisovalnika IPourLogger ni treba razdeliti na tri razrede, lahko vse zapisovalnike združimo v en tip:

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

In če dodamo še četrto vrsto operacije, potem je beleženje zanjo že pripravljeno. In koda samih operacij je čista in brez hrupa infrastrukture.

Kot rezultat, imamo 5 razredov za reševanje problema pitja:

  • Postopek izlivanja
  • Operacija pitja
  • Operacija motenja
  • Logger
  • Fasada za pitje

Vsak od njih je odgovoren izključno za eno funkcionalnost in ima en razlog za spremembo. Vsa pravila, podobna spremembi, se nahajajo v bližini.

Primer iz resničnega življenja

Nekoč smo napisali storitev za samodejno registracijo b2b stranke. In pojavila se je metoda GOD za 200 vrstic podobne vsebine:

  • Pojdite na 1C in ustvarite račun
  • S tem računom pojdite na plačilni modul in ga tam ustvarite
  • Preverite, ali na glavnem strežniku ni bil ustvarjen račun s takim računom
  • Ustvari nov račun
  • Dodajte rezultate registracije v plačilnem modulu in številko 1c v storitev rezultatov registracije
  • V to tabelo dodajte podatke o računu
  • Ustvarite številko točke za to stranko v točkovni storitvi. Tej storitvi posredujte svojo številko računa 1c.

In na tem seznamu je bilo še približno 10 poslovnih operacij s strašno povezljivostjo. Skoraj vsi so potrebovali objekt računa. Pri polovici klicev sta bila potrebna ID točke in ime stranke.

Po eni uri refaktoriranja smo lahko ločili infrastrukturno kodo in nekatere nianse dela z računom v ločene metode/razrede. Božja metoda je to olajšala, vendar je ostalo 100 vrstic kode, ki se preprosto niso želele razvozlati.

Šele po nekaj dneh je postalo jasno, da je bistvo te »lahke« metode poslovni algoritem. In da je bil prvotni opis tehničnih specifikacij precej zapleten. In prav poskus razbitja te metode na koščke bo kršil SRP, in ne obratno.

Formalizem.

Čas je, da pustimo našega pijanca pri miru. Obriši si solze - zagotovo se bova še kdaj vrnila k temu. Zdaj pa formalizirajmo znanje iz tega članka.

Formalizem 1. Opredelitev SRP

  1. Elemente ločite tako, da je vsak od njih odgovoren za eno stvar.
  2. Odgovornost pomeni "razlog za spremembo". To pomeni, da ima vsak element samo en razlog za spremembo, v smislu poslovne logike.
  3. Morebitne spremembe poslovne logike. mora biti lokaliziran. Elementi, ki se spreminjajo sinhrono, morajo biti v bližini.

Formalizem 2. Potrebna merila za samotestiranje.

Nisem zasledil zadostnih meril za izpolnjevanje SRP. Vendar obstajajo potrebni pogoji:

1) Vprašajte se, kaj počne ta razred/metoda/modul/storitev. nanj morate odgovoriti s preprosto definicijo. ( Hvala vam Brightori )

pojasnila

Vendar je včasih zelo težko najti preprosto definicijo

2) Popravljanje napake ali dodajanje nove funkcije vpliva na najmanjše število datotek/razredov. Idealno - eno.

pojasnila

Ker je odgovornost (za funkcijo ali napako) zaprta v eni datoteki/razredu, točno veste, kje iskati in kaj urejati. Na primer: funkcija spreminjanja izhoda operacij beleženja bo zahtevala spremembo samo zapisovalnika. Preostanka kode ni treba preganjati.

Drug primer je dodajanje novega kontrolnika uporabniškega vmesnika, podobnega prejšnjim. Če vas to prisili, da dodate 10 različnih entitet in 15 različnih pretvornikov, je videti, kot da pretiravate.

3) Če več razvijalcev dela na različnih funkcijah vašega projekta, potem je verjetnost konflikta spajanja, to je verjetnost, da bo isto datoteko/razred spremenilo več razvijalcev hkrati, minimalna.

pojasnila

Če morate pri dodajanju nove operacije »Nalijte vodko pod mizo« vplivati ​​na drvar, operacijo pitja in točenja, potem je videti, da so odgovornosti razdeljene krivo. Seveda to ni vedno mogoče, vendar bi morali poskušati zmanjšati to številko.

4) Ko vam postavijo pojasnjevalno vprašanje o poslovni logiki (s strani razvijalca ali upravitelja), greste strogo v en razred/datoteko in prejemate informacije samo od tam.

pojasnila

Funkcije, pravila ali algoritmi so napisani strnjeno, vsaka na enem mestu in niso raztreseni z zastavicami po celotnem prostoru kode.

5) Poimenovanje je jasno.

pojasnila

Naš razred ali metoda je odgovorna za eno stvar in odgovornost se odraža v njenem imenu

AllManagersManagerService - najverjetneje božji razred
Lokalno plačilo - verjetno ne

Formalizem 3. Occam-prva razvojna metodologija.

Na začetku oblikovanja človek opica ne pozna in ne čuti vseh tankosti rešenega problema in lahko naredi napako. Napake lahko naredite na različne načine:

  • Naredite predmete prevelike z združitvijo različnih odgovornosti
  • Preoblikovanje z razdelitvijo posamezne odgovornosti na več različnih vrst
  • Nepravilno določite meje odgovornosti

Pomembno si je zapomniti pravilo: "bolje je narediti veliko napako" ali "če niste prepričani, se ne delite." Če na primer vaš razred vsebuje dve odgovornosti, potem je še vedno razumljiv in ga je mogoče razdeliti na dva z minimalnimi spremembami kode odjemalca. Sestavljanje kozarca iz drobcev stekla je običajno težje zaradi konteksta, ki je razpršen v več datotekah, in pomanjkanja potrebnih odvisnosti v kodi odjemalca.

Čas je, da zaključimo dan

Obseg SRP ni omejen na OOP in SOLID. Velja za metode, funkcije, razrede, module, mikrostoritve in storitve. Velja za razvoj "figax-figax-and-prod" in "rocket-science", s čimer bo svet povsod malo boljši. Če dobro pomislite, je to skoraj temeljno načelo vsega inženirstva. Strojništvo, nadzorni sistemi in pravzaprav vsi kompleksni sistemi so zgrajeni iz sestavnih delov in "premalo razdrobljenost" oblikovalcem odvzame fleksibilnost, "prekomerna razdrobljenost" oblikovalcem odvzame učinkovitost, nepravilne meje pa jim odvzamejo razum in duševni mir.

Načelo ene same odgovornosti. Ni tako preprosto, kot se zdi

SRP ni izumila narava in ni del eksaktne znanosti. Izstopa iz naših bioloških in psiholoških omejitev. Je le način za nadzor in razvoj kompleksnih sistemov z uporabo možganov opičjega človeka. Pove nam, kako razgraditi sistem. Prvotna formulacija je zahtevala precejšnjo mero telepatije, vendar upam, da ta članek odstranjuje nekaj dimne zavese.

Vir: www.habr.com

Dodaj komentar