Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub

Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub Üksikvastutuse põhimõte, mida tuntakse ka kui ühtse vastutuse põhimõte,
on ühtlasi ühtse muutlikkuse põhimõte – äärmiselt libe mõiste ja väga närviline teema programmeerija vestlusel.

Minu esimene tõsine tutvus selle põhimõttega toimus esimese kursuse alguses, kui noored ja rohelised meid viidi metsa, et muuta õpilastest tõelised üliõpilased.

Metsas jagati meid gruppidesse, kus igas oli 8-9 inimest, ja korraldati võistlus - milline grupp joob pudeli viina kiiremini, tingimusel, et grupi esimene inimene valab viina klaasi, teine joob ning kolmas näksib. Esimese ülesande täitnud inimene läheb grupi saba.

Juhtum, kui järjekorra suurus oli kolmega jagatav, oli hea SRP rakendus.

Määratlemine 1. Üksikvastutus.

Üksikvastutuse (SRP) ametlik määratlus ütleb, et igal objektile on oma vastutus ja olemasolu põhjus ning sellel on ainult üks vastutus.

Vaatame objekti „Käitur“ (Tippler).
Üksikvastutuse rakendamiseks jagame kohustused kolmele:

  • Üks valab (PourOperation)
  • Üks joob (DrinkUpOperation)
  • Üks näksib (TakeBiteOperation)

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

Joogimees on seevastu nende toimingute fassaad:

class Tippler {
    \/\/...\n    void Act(){\n        _pourOperation.Do() \/\/ valada\n        _drinkUpOperation.Do() \/\/ juua\n        _takeBiteOperation.Do() \/\/ näksida\n    }\n}

Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub

Miks?

Inimprogrammeerija kirjutab koodi inimese-ahvi jaoks ning inimene-ahv on tähelepanematu, rumal ja pidevalt kuhugi kiirus. Ta suudab korraga hoida ja mõista umbes 3 - 7 mõistet.
Joogimees puhul on neid mõisteid kolm. Kuid kui kirjutame kogu koodi üheks lõiguks, tulevad sinna käed, klaasid, kaklused ja lõputud poliitika arutelud. Ja kõik see on ühe meetodi kehas. Olen kindel, et olete sellist koodi oma praktikas näinud. Mitte kõige inimlikum katsumus psüühikale.

Teisest küljest on inimesest ahvidel õnnestunud modelleerida tõelise maailma objekte oma peas. Oma kujutluses saab ta neid kokku lükata, uusi objekte neist kokku panna ja samuti lahti võtta. Kujutage ette vana auto mudelit. Te võite kujutluses avada ukse, eemaldada uksekatte ja näha seal aknatõstukimehhanisme, mille sees on hammasrattaid. Kuid te ei saa kõiki auto komponente korraga näha, ühes „loenduses“. Ükskõik kui vähemalt „inimesest ahv“ ei saa.

Seetõttu dekomponeerivad programmijad keerulisi mehhanisme lihtsamaks ja töötavateks elementideks. Kuid dekomponeerida saab erinevalt: paljudes vanades autodes väljub õhutorustik uksest, samas kui kaasaegsetes ei lase luku elektroonika rike mootorit käivitada, mis tekitab remondi ajal probleeme.

Nii et SRP on põhimõte, mis selgitab KUID dekomponeerida, s.t. kus joon alla tõmmata..

Ta ütleb, et dekodeerimise põhimõte peab olema „vastutuse“ jagamine, st erinevate objektide ülesannete järgi.

Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub

Naaseme joodiku juurde ja plusside juurde, mida inimene-ape saab dekomponeerimise käigus:

  • Kood on igal tasemel äärmiselt selge.
  • Koodi saavad kirjutada mitmed programmeerijad korraga (igaühel on oma eraldi element).
  • Automaatne testimine muutub lihtsamaks - mida lihtsam on element, seda lihtsam on seda testida.
  • Koodi kompositsioonilisus suureneb - saate asendada. DrinkUpOperation operatsiooniga, kus joodik kallab vedelikku laua alla. Või asendada valamise operatsioon operatsiooniga, kus segate veini ja vett või viina ja õlut. Vastavalt ärinõuetele saate teha kõike, ilma et puudutaksite meetodi koodi. Tippler.Act.
  • Nendest operatsioonidest saate kokku panna ülesööja (kasutades vaid TakeBitOperation), alkohooliku (kasutades vaid DrinkUpOperation otse pudelist) ja rahuldada paljusid muid äri nõudeid.

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

Ja muidugi miinused:

  • Tuleb luua rohkem tüüpe.
  • Joodik joob esmakordselt paar tundi hiljem, kui võiks.

Määratlemine 2. Üksik muutlikkus.

Lubage, härrased! Käituri klass täidab samuti ühtset vastutust — ta joob! Ja üldsegi, sõna „vastutus“ on äärmiselt hägune mõte. Keegi on vastutav inimkonna saatuse eest, samas kui keegi on vastutav nende pingviinide tõstmiseks, mis on poolusel ümber kukkunud.

Vaatame kahte joobnute rakendust. Esimene, nagu eespool mainitud, sisaldab kolme klassi — valamine, joomine ja snäkkimine.

Teine on kirjutatud „Eespool ja ainult eespool“ metoodika kaudu ning sisaldab kogu loogikat meetodis. Act:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
с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 need klassid, väliselt vaatleja silmis, näevad välja täiesti ühesugused ja täidavad ühtset vastutust „juua“.

Piinlik!

Siis vaatasime internetist ja õppisime SRP teise määratlemise — Ühe Muutuvuse Printsiibi (Single Changeability Principle).

SCP ütleb, et „Moodulil on üks ja ainult üks põhjus, miks seda muuta“. See tähendab, et „Vastutus on muutumise põhjus“.

(Tundub, et need kutid, kes algse määratluse välja mõtlesid, uskusid inimese-ape telepaatilistesse võimetesse)

Nüüd on kõik omal kohal. Eraldi saame muuta valamis-, joomis- ja snäkkimisprotseduure, ja joobnute puhul saame muuta ainult toimingute järjestust ja koostist, näiteks liigutades snacks enne joomist või lisades toosti lugemise.

„Eespool ja ainult eespool“ lähenemises võib kõik, mida saab muuta — muutuda ainult meetodis. Act. See võib olla loetav ja tõhus, kui loogikat on vähe ja see harva muutub, kuid tihti lõpeb see kohutavate meetoditega, igaühes 500 rida, kus if-e on rohkem, kui oleks vajalik Venemaa NATOsse astumise jaoks.

Määratlemine 3. Muutuste lokaliseerimine.

Joobnud inimesed ei mõista sageli, miks nad ärkasid kellegi teise korteris või kus nende mobiiltelefon on. On aeg lisada detailne logimine.

Alustame logimist valamisprotsessist:

class PourOperation: IOperation{
    PourOperation(ILogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.Log($"Enne valamist koos {_hand} ja {_bottle}");
        //Valamise äri loogika ...
        _log.Log($"Pärast valamist koos {_hand} ja {_bottle}");
    }
}

Kapseldades selle PourOperation, tegime m wise vastutuse ja kapseldamise aspektist, kuid muutsime nüüd muutlikkuse põhimõttega hädas. Peale operatsiooni, mis võib muutuda, muutub ka logimine. Peame eraldama ja looma spetsiaalse logijate süsteemi valamise operatsiooni jaoks:

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{
             //... äriloogika
             _log.LogAfter(_hand, _bottle);
        }
        catch(exception e){
            _log.OnError(_hand, _bottle, e)
        }
    }
}

Täpne lugeja märkab, et LogAfter, LogBefore ja OnError võivad samuti eraldi muutuda, ja eelnevate tegevustega sarnaselt luuakse kolm klassi: PourLoggerBefore, PourLoggerAfter ja PourErrorLogger.

Ja meenutades, et joodikute operatsioone on kolm — saame kokku üheksa logimisklassi. Lõppkokkuvõttes koosneb kogu joodik 14 (!!!) klassist.

Hüperbool? Kahtlemata! Inimene-elevant koos dekodeerimise granaadiga lõhestab 'valaja' krahvi, klaasi, valamise operaatoriteks, veeteeninduse teenuseks, molekulide kokkupõrke füüsiliseks mudeliks ja järgmise kvartali jooksul püüab ta lahendada sõltuvusi ilma globaalsete muutujateta. Ja uskuge mind — ta ei peatu.

Just sel hetkel jõuavad paljud järeldusele, et SRP on roosiliste kuningriikide muinasjutud, ja hakkavad mõtteid rippuma…

… kuna nad ei tea kolmandast definitsioonist Srp:

„Ühe vastutuse põhimõte ütleb, et sarnased muutustega asjad peaksid olema ühes kohas“. või “Asjad, mis muutuvad koos, peaksid olema ühes kohas.

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

See on väga oluline punkt — kuna kõik SRP selgitused, mis eelnevalt olid, rääkisid sellest, et tüüpe tuleb purustada seni, kuni nad purunevad, st see seadis objekti suurusele „ülemise piirangu“, nüüd räägime me ka „alumise piirangu“ üle. Teisisõnu, SRP mitte ainult ei nõua „purustamist seni, kuni see puruneb“, vaid ka seda, et mitte liialdada — „ära purusta omavahel seotud asju“.. See on suur lahing Ockhami habeme ja inimese-eleva vahel!

Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub

Nüüd peaks joodikul olema lihtsam. Peale selle, et IPourLogger logijate jagamist kolmes klassis pole, saame ka kõik logijad kokku ühendada ühte tüüp:

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 meie neljas operatsioonitüüp lisandub, on logimine selle jaoks juba valmis. Ja operatsioonide kood ise on puhas ning vabastatud infrastruktuuri mürast.

Seetõttu on meil viis klassi ülesande täitmiseks:

  • Valamise operatsioon
  • Joomise operatsioon
  • Söögioperatsioon
  • Logija
  • Joobnute fasad

Igaüks neist vastutab rangelt ühe funktsionaalsuse eest, omades ühe muutmise põhjust. Kõik sarnased muutmisreeglid on läheduses.

Näide reaalsest elust

Kord kirjutasime B2B kliendi automaatse registreerimise teenust. Ja ilmnes jumala meetod, mille pikkus oli 200 rida sarnast sisu:

  • Mine 1C-sse ja ava konto
  • Selle kontoga mine maksemoodulisse ja registreeri see seal
  • Kontrolli, et sellise kontoga konto ei oleks peamises süsteemis loodud serveris
  • Loo uus konto
  • Registreerimise tulemused maksemoodulis ja 1C number lisa registreerimise tulemuste teenusesse
  • Lisa sellesse tabelisse konto teave
  • Loo punktinumber selle kliendi jaoks punktiteenuses. Edasta sellele teenusele 1C konto number.

Ja selles nimekirjas oli veel umbes 10 äritegevust, millel oli kohutav omavaheline sõltuvus. Konto objekt oli vajalik peaaegu kõigile. Punkti identifikaator ja kliendi nimi olid vajalikud pooled kutsed.

Pärast tunni pikkust refaktoreerimist suutsime eraldada infrastruktuuri koodi ja mõned konto töö nüansid eraldi meetoditeks/klassideks. Jumala meetod kergendus, kuid jäänud oli 100 koodirida, mis ei soovinud end kududa.

Alles paar päeva hiljem tuli arusaam, et selle „kergendatud“ meetodi sisuks on ärialgoritm. Ja et algne TKa kirjelduse koostamine oli üsna keeruline. Just selle meetodi tükkideks jagamise katse on SRP rikkumine, mitte vastupidi.

Formalism.

On aeg jätta meie joobnukesed rahule. Kustuta pisarad — me kindlasti naaseme tema juurde kunagi. Ja nüüd formaliseerime selle artikli teadmised.

Formalism 1. SRP määratlemine

  1. Eraldage elemendid nii, et igaühel neist oleks ainult üks vastutus.
  2. Vastutus tõlgitakse kui „muudatuse põhjus“. See tähendab, et igal elemendil on ainult üks põhjus muutuseks, äriloogika mõistes.
  3. Potentsiaalsed muudatused äriloogikas peavad olema lokaliseeritud. Muudetavad sünkroonsed elemendid peaksid olema kõrvuti.

Formalisme 2. Vajalikud isetõendi kriteeriumid.

Ma ei ole kohtunud piisavate SRP täitmise kriteeriumidega. Kuid on mõned vajalikud tingimused:

1) Küsi endalt küsimus - mida teeb see klass/metod/moodul/teenus. Pead sellele vastama lihtsa määratlusega. (Aitäh Brightori )

selgitused

Kuid vahel on lihtsa määratluse leidmine väga keeruline

2) Teatud vea parandamine või uue funktsiooni lisamine mõjutab minimaalset arvu faile/klasse. Ideaalis - üks.

selgitused

Kuna vastutus (funktsiooni või vea) on kapseldatud ühte faili/klassidesse, tead kindlalt, kus otsida ja mida muuta. Näiteks: funktsioon, mis muudab logimise väljundit, nõuab muutmist ainult logijalt. Teise koodi vahtimise ei ole vaja.

Teine näide - uue kasutajaliidese kontrolli lisamine, mis sarnaneb eelnevale. Kui see sunnib sind lisama 10 erinevat elementi ja 15 erinevat konverteerijat - tundub, et oled "liialdanud".

3) Kui mitmed arendajad töötavad erinevate funktsioonide kallal teie projektis, on tõenäosus merge-konflikti, st tõenäosus, et sama faili/klassiga muudetakse korraga mitme arendaja poolt - minimaalne.

selgitused

Kui uue operatsiooni „Valada viina laua alla“ lisamisel peate puutuma kokku logijaga, joomise ja valamise operatsiooniga, siis näib, et vastutused on halvasti jaotatud. Kindlasti pole see alati võimalik, kuid tuleb püüda seda näitajat vähendada.

4) Täpsustava küsimuse puhul äriloogika kohta (arendajalt või juhilt) kaevud rangelt ühte klassi/ faili ja saad teavet ainult sealt.

selgitused

Funktsioonid, reeglid või algoritmid on kompaktselt kirjutatud igaühes ühes kohas, mitte laiali visatud lippudena üle kogu koodi ruumi.

5) Nimeandmine on arusaadav.

selgitused

Meie klass või metod on vastutav millegi ühe eest, ja vastutus peegeldub selle nimes.

AllManagersManagerService - tõenäoliselt Jumal-klass
LocalPayment - tõenäoliselt mitte

Formalism 3. „Okkami esimese“ arendamise meetodika.

Projekti kavandamise alguses ei tea inimesest ahv kõiki lahendatava ülesande nüansse ja võib eksida. Eksida saab erinevalt:

  • Teha liiga suuri objekte, ühendades erinevaid vastutusi
  • Hävitada, jagades ühe vastutuse paljude erinevate tüüpide peale
  • Vale vastutuse piiride määratlemine

On oluline meeles pidada reeglit: „on parem eksida suurel poolel“, või „kui pole kindel — ei jahuta“. Kui näiteks teie klass koondab endasse kaks vastutust — on see endiselt arusaadav ja seda saab jagada kaheks minimaalsete muudatustega kliendikoodis. Kildudest klaassi kogumine on reeglina keerulisem, kuna kontekst on hajutatud mitmesse faili ja kliendikoodis puuduvad vajalikud sõltuvused.

On aeg lõpetada

SRP kasutusala ei piirdu OOP ja SOLIDiga. See on rakendatav meetoditele, funktsioonidele, klassidele, moodulitele, mikroteenustele ja teenustele. Seda saab rakendada nii „figaks-figaks-ja-ühe-kordselt” kui ka „rakett-teadus” arenduses, muutes igal pool maailma veidi paremaks. Kui mõelda, siis see on peaaegu kogu inseneritehnika fundamentaalne põhimõte. Masinatootmine, juhtimisse süsteemid ja üldiselt kõik keerulised süsteemid — ehitatakse komponentidest, ning „ühed pole piisavad” lõhub ehitajate paindlikkust, „liigne killustamine” — efektiivsust, ja valed piirid — arusaama ja vaimset rahu.

Ühe vastutuse printsiip. Ei ole nii lihtne, nagu tundub

SRP ei ole loodud looduse poolt ega ole osa täpsest teadusest. See tuleneb meie bioloogilistest ja psühholoogilistest piirangutest. See on lihtsalt viis, kuidas kontrollida ja arendada keerulisi süsteeme inimesena-äsja. See ütleb meile, kuidas dekomponeerida süsteemi. Algne sõnastus nõudis märkimisväärset telepaatia oskust, kuid loodan, et see artikkel ajas veidi suitsukatet selgemaks.

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster