Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub

Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub Ühtse vastutuse põhimõte, tuntud ka kui ühtse vastutuse põhimõte,
aka ühtlase varieeruvuse printsiip - äärmiselt libe tüüp aru saada ja selline närviline küsimus programmeerija intervjuul.

Minu esimene tõsine tutvus selle põhimõttega toimus esimese kursuse alguses, kui noored ja rohelised viidi metsa, et teha vaststest õpilasi - päris tudengeid.

Metsas jagati meid 8-9-liikmelistesse gruppidesse ja võisteldi – kumb grupp joob kõige kiiremini ära pudeli viina, eeldusel, et grupist esimene valab viina klaasi, teine ​​joob ära, ja kolmas näksib. Toimingu lõpetanud üksus liigub rühma järjekorra lõppu.

Juhtum, kus järjekorra suurus oli kolmekordne, oli SRP hea rakendus.

Definitsioon 1. Üksikvastutus.

Ühtse vastutuse põhimõtte (Single Responsibility Principle, SRP) ametlik definitsioon ütleb, et igal üksusel on oma vastutus ja olemasolu põhjus ning tal on ainult üks vastutus.

Mõelge objektile "Joodik" (Kallutaja).
SRP põhimõtte rakendamiseks jagame kohustused kolmeks:

  • Üks valab (PourOperation)
  • Üks joob (DrinkUpOperation)
  • Üks on suupisteid (TakeBiteOperation)

Iga protsessis osaleja vastutab protsessi ühe komponendi eest, see tähendab, et tal on üks tuumavastutus - juua, valada või näksida.

Joogiauk on omakorda fassaad nende toimingute jaoks:

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

Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub

Miks?

Inimprogrammeerija kirjutab ahvimehele koodi ja ahvimees on tähelepanematu, rumal ja alati kiirustav. Ta suudab hoida ja mõista umbes 3–7 terminit korraga.
Joodiku puhul on neid termineid kolm. Kui aga koodi kirjutada ühe lehega, siis see sisaldab käsi, prille, kaklusi ja lõputuid vaidlusi poliitika üle. Ja kõik see on ühe meetodi kehas. Olen kindel, et olete sellist koodi oma praktikas näinud. Pole just kõige humaansem test psüühikale.

Teisest küljest on ahvimees loodud oma peas reaalse maailma objekte simuleerima. Oma kujutluses saab ta need kokku lükata, neist uusi esemeid kokku panna ja samamoodi lahti võtta. Kujutage ette vana mudelautot. Oma kujutluses saate avada ukse, keerata ukseliistud lahti ja näha seal aknatõstemehhanisme, mille sees on hammasrattad. Kuid kõiki masina komponente korraga, ühes "loendis" näha ei saa. Vähemalt "ahvimees" ei saa.

Seetõttu jagavad inimprogrammeerijad keerukad mehhanismid vähem keerukateks ja töötavateks elementideks. Lagundada saab aga erineval moel: paljudel vanadel autodel läheb õhukanal uksest sisse ja tänapäevastel autodel ei lase mootoril käivituda luku elektroonika rike, mis võib remondi käigus probleemiks osutuda.

Nüüd SRP on põhimõte, mis selgitab, KUIDAS laguneda, st kuhu tõmmata eraldusjoon.

Ta ütleb, et on vaja laguneda vastavalt "vastutuse" jaotuse põhimõttele, see tähendab teatud objektide ülesannetele.

Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub

Pöördume tagasi joomise ja eeliste juurde, mida ahvimees lagunemise ajal saab:

  • Kood on muutunud äärmiselt selgeks igal tasandil
  • Koodi saab kirjutada mitu programmeerijat korraga (igaüks kirjutab eraldi elemendi)
  • Automatiseeritud testimine on lihtsustatud – mida lihtsam element, seda lihtsam on testida
  • Ilmub koodi kompositsioonilisus - saate asendada DrinkUpOperation operatsioonile, mille käigus joodik valab laua alla vedelikku. Või asendage valamine toiminguga, mille käigus segate veini ja vett või viina ja õlut. Sõltuvalt ärinõuetest saate teha kõike ilma meetodi koodi puudutamata Kallutaja.Tegutsema.
  • Nende toimingutega saate gluttoni voltida (kasutades ainult TakeBitOperation), Alkohoolne (kasutavad ainult DrinkUpOperation otse pudelist) ja vastama paljudele muudele ärinõuetele.

(Oh, tundub, et see on juba OCP põhimõte ja ma rikkusin selle postituse vastutust)

Ja muidugi miinused:

  • Peame looma rohkem tüüpe.
  • Joodik joob esimest korda paar tundi hiljem, kui muidu oleks.

Definitsioon 2. Ühtne varieeruvus.

Lubage, härrased! Joogiklassil on ka üksainus vastutus – ta joob! Ja üldiselt on sõna “vastutus” äärmiselt ebamäärane mõiste. Keegi vastutab inimkonna saatuse eest ja keegi vastutab pooluse ümber lükatud pingviinide kasvatamise eest.

Vaatleme jooturi kahte teostust. Esimene, eespool mainitud, sisaldab kolme klassi – vala, joo ja suupiste.

Teine on kirjutatud metoodika "Edasi ja ainult edasi" kaudu ja sisaldab kogu meetodi loogikat tegu:

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

Mõlemad klassid näevad välise vaatleja seisukohast välja täpselt ühesugused ja jagavad sama "joomise" vastutust.

Segadus!

Seejärel läheme võrku ja leiame veel ühe SRP määratluse – ühtse muutuvuse printsiibi.

SCP väidab, et "Moodulil on muutmiseks üks ja ainus põhjus". See tähendab: "Vastutus on muutuste põhjus."

(Tundub, et poisid, kes algse määratluse välja mõtlesid, olid kindlad ahvimehe telepaatilistes võimetes)

Nüüd loksub kõik paika. Eraldi saame muuta kallamise, joomise ja näksimise protseduure, kuid joojas endas saame muuta vaid toimingute järjekorda ja koostist, näiteks liigutades vahepala enne joomist või lisades röstsaia näidu.

"Edasi ja ainult edasi" lähenemise korral muudetakse kõike, mida saab muuta, ainult meetodis tegu. See võib olla loetav ja tõhus, kui loogikat on vähe ja see muutub harva, kuid sageli lõpeb see kohutavate 500-realiste meetoditega, milles on rohkem kui-avaldusi, kui Venemaa NATO-ga liitumiseks vajalik.

Definitsioon 3. Muudatuste lokaliseerimine.

Joodikud ei saa sageli aru, miks nad ärkasid kellegi teise korteris või kus on nende mobiiltelefon. On aeg lisada üksikasjalik logimine.

Alustame logimist valamise protsessiga:

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

Kapseldades selle sisse PourOperation, käitusime vastutuse ja kapseldumise seisukohalt targalt, kuid nüüd oleme segaduses variatiivsuse põhimõttega. Lisaks toimingule endale, mis võib muutuda, muutub muudetavaks ka logi ise. Valamistoimingu jaoks peate eraldama ja looma spetsiaalse logija:

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

Põhjalik lugeja märkab seda LogAfter, Logi Enne и OnError saab ka individuaalselt muuta ja analoogselt eelmiste sammudega loob kolm klassi: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

Ja meenutades, et joodiku jaoks on kolm operatsiooni, saame üheksa raieklassi. Sellest tulenevalt koosneb kogu joogiring 14 (!!!) klassist.

Hüperbool? Vaevalt! Lagunemisgranaadiga ahvimees jagab “valaja” karahviniks, klaasiks, valamisoperaatoriteks, veevarustusteenuseks, molekulide kokkupõrke füüsiliseks mudeliks ja proovib järgmise kvartali jooksul sõltuvusi lahti harutada ilma. globaalsed muutujad. Ja uskuge mind, ta ei peatu.

Just sel hetkel jõuavad paljud järeldusele, et SRP on muinasjutud roosadest kuningriikidest, ja lähevad nuudleid mängima...

... ilma Srp kolmanda definitsiooni olemasolust kunagi teada saamata:

„Ühtse vastutuse põhimõte ütleb seda muutustega sarnaseid asju tuleks hoida ühes kohas". või "Mis muudatused koos tuleks hoida ühes kohas"

See tähendab, et kui muudame toimingu logimist, siis peame seda ühes kohas muutma.

See on väga oluline punkt - kuna kõik ülaltoodud SRP selgitused ütlesid, et purustamise ajal on vaja tüüpe purustada, see tähendab, et nad kehtestasid objekti suurusele "ülemise piiri" ja nüüd me räägime juba "alampiirist" . Teisisõnu, SRP ei nõua mitte ainult purustamist purustamise ajal, vaid ka mitte üle pingutama - "ärge purustage üksteisega seotud asju". See on suurepärane lahing Occami habemenuga ja ahvimehe vahel!

Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub

Nüüd peaks joodik end paremini tundma. Lisaks sellele, et IPourLoggeri logijat pole vaja jagada kolmeks klassiks, saame ka kõik logijad ühte tüüpi ühendada:

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

Ja kui lisame neljanda toimingutüübi, siis on selle logimine juba valmis. Ja toimingute endi kood on puhas ja taristumürata.

Selle tulemusena on meil joomise probleemi lahendamiseks 5 klassi:

  • Valamise operatsioon
  • Joogioperatsioon
  • Kinnitusoperatsioon
  • Raiemees
  • Joogi fassaad

Igaüks neist vastutab rangelt ühe funktsiooni eest ja neil on üks põhjus muutusteks. Kõik muudatusega sarnased reeglid asuvad läheduses.

Näide päris elust

Kunagi kirjutasime teenuse b2b kliendi automaatseks registreerimiseks. Ja 200 sarnase sisu rea jaoks ilmus JUMALA meetod:

  • Avage 1C ja looge konto
  • Selle kontoga minge maksemoodulisse ja looge see seal
  • Kontrollige, et põhiserveris poleks sellise kontoga kontot loodud
  • Looge uus konto
  • Lisage registreerimise tulemused maksemoodulisse ja 1c number registreerimistulemuste teenusesse
  • Lisage sellesse tabelisse konto teave
  • Looge sellele kliendile punktiteenuses punktinumber. Edastage oma 1c kontonumber sellele teenusele.

Ja selles loendis oli veel umbes 10 kohutava ühenduvusega äritegevust. Peaaegu kõik vajasid kontoobjekti. Poolte kõnede puhul läks vaja punkti ID-d ja kliendi nime.

Pärast tunniajalist ümbertöötamist suutsime eraldada infrastruktuuri koodi ja mõned kontoga töötamise nüansid eraldi meetoditeks/klassideks. Jumala meetod tegi selle lihtsamaks, kuid järele jäi 100 koodirida, mis lihtsalt ei tahtnud lahti harutada.

Alles mõne päeva pärast sai selgeks, et selle "kerge" meetodi olemus on ärialgoritm. Ja et tehniliste kirjelduste esialgne kirjeldus oli üsna keeruline. Ja just katse seda meetodit tükkideks jagada rikub SRP-d ja mitte vastupidi.

Formalism.

On aeg oma joodik rahule jätta. Kuivatage oma pisarad – me tuleme selle juurde kunagi kindlasti tagasi. Nüüd vormistame sellest artiklist saadud teadmised.

Formalism 1. SRP definitsioon

  1. Eraldage elemendid nii, et igaüks neist vastutaks ühe asja eest.
  2. Vastutus tähendab "muutmise põhjust". See tähendab, et igal elemendil on äriloogika seisukohalt muutmiseks ainult üks põhjus.
  3. Võimalikud muudatused äriloogikas. tuleb lokaliseerida. Sünkroonselt muutuvad elemendid peavad olema läheduses.

Formalism 2. Vajalikud enesekontrolli kriteeriumid.

Ma ei ole näinud piisavaid kriteeriume SRP täitmiseks. Kuid selleks on vajalikud tingimused:

1) Küsige endalt, mida see klass/meetod/moodul/teenus teeb. peate sellele vastama lihtsa määratlusega. ( Aitäh Brightori )

selgitused

Mõnikord on aga lihtsat määratlust väga raske leida

2) Vea parandamine või uue funktsiooni lisamine mõjutab minimaalset failide/klasside arvu. Ideaalis - üks.

selgitused

Kuna vastutus (funktsiooni või vea eest) on koondatud ühte faili/klassi, teate täpselt, kust otsida ja mida muuta. Näiteks: logitoimingute väljundi muutmise funktsioon nõuab ainult logija muutmist. Ülejäänud koodi pole vaja läbi joosta.

Teine näide on uue kasutajaliidese juhtelemendi lisamine, mis on sarnane eelmistele. Kui see sunnib teid lisama 10 erinevat olemit ja 15 erinevat muundurit, tundub, et pingutate sellega üle.

3) Kui mitu arendajat töötavad teie projekti erinevate funktsioonide kallal, on liitmiskonflikti tõenäosus, st tõenäosus, et sama faili/klassi muudab samaaegselt mitu arendajat, minimaalne.

selgitused

Kui uue toimingu “Vala viina laua alla” lisamisel tuleb mõjutada logirajat, joomise ja kallamise toimingut, siis tundub, et kohustused on kõveralt jagatud. Muidugi pole see alati võimalik, kuid me peaksime püüdma seda arvu vähendada.

4) Kui küsitakse äriloogikat täpsustavat küsimust (arendajalt või juhilt), siis lähed rangelt ühte klassi/faili ja saad infot ainult sealt.

selgitused

Funktsioonid, reeglid või algoritmid on kirjutatud kompaktselt, igaüks ühes kohas ja ei ole lippudega üle koodiruumi laiali.

5) Nimetus on selge.

selgitused

Meie klass või meetod vastutab ühe asja eest ja vastutus peegeldub selle nimes

AllManagersManagerService – tõenäoliselt Jumala klass
LocalPayment – ​​ilmselt mitte

Formalism 3. Occam-esimese arenduse metoodika.

Disaini alguses ei tea ega tunne ahvimees lahendatava probleemi kõiki peensusi ning võib eksida. Saate teha vigu mitmel viisil:

  • Muutke objektid liiga suureks, ühendades erinevad kohustused
  • Ümberkujundamine, jagades ühe vastutuse mitmeks erinevaks tüübiks
  • Määratle vastutuse piirid valesti

Oluline on meeles pidada reeglit: "parem on teha suur viga" või "kui te pole kindel, ärge jagage seda." Kui sinu klassis on näiteks kaks kohustust, siis on see siiski arusaadav ja kliendikoodi minimaalsete muudatustega kaheks jaotav. Klaasikildudest klaasi kokkupanek on tavaliselt keerulisem, kuna kontekst on hajutatud mitmele failile ja kliendikoodis puuduvad vajalikud sõltuvused.

On aeg seda päevaks nimetada

SRP ulatus ei piirdu ainult OOP-i ja SOLID-iga. See kehtib meetodite, funktsioonide, klasside, moodulite, mikroteenuste ja teenuste kohta. See kehtib nii "figax-figax-and-prod" kui ka "raketiteaduse" arenduse kohta, muutes maailma kõikjal natuke paremaks. Kui järele mõelda, on see peaaegu kogu inseneritöö aluspõhimõte. Masinaehitus, juhtimissüsteemid ja õigupoolest kõik keerulised süsteemid on üles ehitatud komponentidest ning “alakillustamine” võtab disaineritelt paindlikkuse, “ülekillustamine” võtab disaineritelt ära tõhususe ning valed piirid võtavad neilt mõistuse ja meelerahu.

Ühtse vastutuse põhimõte. Mitte nii lihtne, kui tundub

SRP ei ole looduse poolt välja mõeldud ega kuulu täppisteaduse hulka. See murrab välja meie bioloogilistest ja psühholoogilistest piirangutest.See on lihtsalt viis juhtida ja arendada keerulisi süsteeme, kasutades ahvi-inimese aju. Ta räägib meile, kuidas süsteemi lagundada. Algne sõnastus nõudis üsna palju telepaatiat, kuid ma loodan, et see artikkel puhastab osa suitsukattest.

Allikas: www.habr.com

Lisa kommentaar