Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo

Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo Vienos atsakomybės principas, dar žinomas kaip vienos atsakomybės principas,
aka vienodo kintamumo principas - be galo slidus vaikinas suprasti ir toks nervingas klausimas programuotojo interviu.

Mano pirmoji rimtesnė pažintis su šiuo principu įvyko pirmo kurso pradžioje, kai jauniklius ir žaliuosius išvedė į mišką iš lervų daryti mokinius – tikrus mokinius.

Miške buvome suskirstyti į grupeles po 8-9 žmones ir surengėme varžybas – kuri grupė greičiausiai išgers degtinės butelį, su sąlyga, kad pirmas žmogus iš grupės pila degtinę į taurę, antrasis išgeria, o trečias užkandžiauja. Vienetas, kuris baigė savo veiklą, pereina į grupės eilės pabaigą.

Atvejis, kai eilės dydis buvo trijų kartotinis, buvo geras SRP įgyvendinimas.

Apibrėžimas 1. Viena atsakomybė.

Oficialus bendros atsakomybės principo (SRP) apibrėžimas teigia, kad kiekvienas subjektas turi savo atsakomybę ir egzistavimo priežastį, ir jis turi tik vieną atsakomybę.

Apsvarstykite objektą „Gertuoklis“ (Tippleris).
Siekdami įgyvendinti SRP principą, atsakomybes padalinsime į tris:

  • Vienas pila (PourOperation)
  • Vienas gėrimas (DrinkUpOperation)
  • Vienas užkandžiauja (TakeBiteOperation)

Kiekvienas proceso dalyvis yra atsakingas už vieną proceso komponentą, tai yra, turi vieną atominę atsakomybę – išgerti, užpilti ar užkąsti.

Savo ruožtu geriamoji anga yra šių operacijų fasadas:

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

Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo

Kodėl?

Žmogaus programuotojas rašo kodą beždžioniam žmogui, o beždžioninis žmogus yra nedėmesingas, kvailas ir visada skubantis. Jis vienu metu gali turėti ir suprasti apie 3–7 terminus.
Girtuoklio atveju yra trys iš šių terminų. Tačiau jei kodą užrašysime vienu lapu, tai jame bus rankos, akiniai, muštynės ir begalės ginčų apie politiką. Ir visa tai bus vieno metodo kūne. Esu tikras, kad tokį kodą matėte savo praktikoje. Ne pats humaniškiausias išbandymas psichikai.

Kita vertus, beždžionės žmogus yra sukurtas imituoti realaus pasaulio objektus savo galvoje. Savo vaizduotėje jis gali juos sustumti, surinkti iš jų naujus objektus ir tuo pačiu būdu juos išardyti. Įsivaizduokite seno modelio automobilį. Savo vaizduotėje galite atidaryti duris, atsukti durų apdailą ir pamatyti ten langų pakėlimo mechanizmus, kurių viduje bus krumpliaračiai. Bet jūs negalite pamatyti visų mašinos komponentų vienu metu, viename „sąraše“. Bent jau „žmogus beždžionė“ negali.

Todėl žmonių programuotojai suskaido sudėtingus mechanizmus į mažiau sudėtingų ir veikiančių elementų rinkinį. Tačiau jis gali būti skaidomas įvairiai: daugelyje senų automobilių ortakis patenka į duris, o šiuolaikiniuose automobiliuose spynos elektronikos gedimas neleidžia užvesti varikliui, o tai gali sukelti problemų remonto metu.

Dabar SRP yra principas, paaiškinantis, KAIP suskaidyti, tai yra, kur nubrėžti skiriamąją liniją.

Jis sako, kad reikia skaidyti pagal „atsakomybės“ padalijimo principą, tai yra pagal tam tikrų objektų užduotis.

Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo

Grįžkime prie gėrimo ir privalumų, kuriuos beždžionė gauna skilimo metu:

  • Kodas tapo itin aiškus visais lygmenimis
  • Kodą vienu metu gali parašyti keli programuotojai (kiekvienas rašo atskirą elementą)
  • Automatinis testavimas yra supaprastintas – kuo elementas paprastesnis, tuo lengviau jį išbandyti
  • Pasirodo kodo sudėtis - galite pakeisti DrinkUpOperation į operaciją, kurios metu girtas pila skystį po stalu. Arba pakeiskite pylimo operaciją vyno ir vandens arba degtinės ir alaus maišymo operacija. Priklausomai nuo verslo reikalavimų, viską galite padaryti neliesdami metodo kodo Tippleris.Aktas.
  • Iš šių operacijų galite sulankstyti gluttoną (naudodami tik TakeBitOperation), Alkoholinis (naudojant tik DrinkUpOperation tiesiai iš butelio) ir atitinka daugelį kitų verslo reikalavimų.

(O, atrodo, kad tai jau OCP principas, ir aš pažeidžiau atsakomybę už šį įrašą)

Ir, žinoma, minusai:

  • Turėsime sukurti daugiau tipų.
  • Girtas pirmą kartą išgeria porą valandų vėliau, nei būtų išgėręs.

2 apibrėžimas. Vieningas kintamumas.

Leiskite man, ponai! Gėrimo klasė taip pat turi vienintelę atsakomybę – ji geria! Ir apskritai žodis „atsakomybė“ yra labai miglota sąvoka. Kažkas yra atsakingas už žmonijos likimą, o kažkas atsakingas už pingvinų, kurie buvo apvirtę prie ašigalio, iškėlimą.

Panagrinėkime du gertuvės įgyvendinimus. Pirmajame, minėtame, yra trys klasės – užpilkite, gerkite ir užkandžiaukite.

Antrasis yra parašytas naudojant „Pirmyn ir tik pirmyn“ metodiką ir apima visą metodo logiką aktas:

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

Abi šios klasės, žiūrint iš išorės, atrodo visiškai vienodai ir dalijasi ta pačia „gėrimo“ atsakomybe.

Sumišimas!

Tada prisijungiame prie interneto ir išsiaiškiname kitą SRP apibrėžimą – vieno keitimo principą.

SCP teigia, kad "Modulis turi vieną ir vienintelę priežastį keisti“. Tai yra: „Atsakomybė yra pokyčių priežastis“.

(Atrodo, kad vaikinai, kurie sugalvojo pirminį apibrėžimą, buvo įsitikinę beždžionės žmogaus telepatiniais sugebėjimais)

Dabar viskas stoja į savo vietas. Atskirai galime keisti pilstymo, gėrimo ir užkandžiavimo procedūras, tačiau pačiame girtuoklyje galime pakeisti tik operacijų seką ir sudėtį, pavyzdžiui, užkandį perkeliant prieš geriant arba pridėjus skrebučio skaitymą.

Taikant metodą „Pirmyn ir tik pirmyn“, viskas, ką galima pakeisti, keičiama tik metodu aktas. Tai gali būti įskaitoma ir veiksminga, kai mažai logikos ir ji retai keičiasi, bet dažnai baigiasi siaubingais metodais, kurių kiekvienoje yra 500 eilučių, su daugiau if-teiginių, nei reikia Rusijai prisijungti prie NATO.

Apibrėžimas 3. Pokyčių lokalizavimas.

Girtaujantys dažnai nesupranta, kodėl jie pabudo svetimame bute, kur yra jų mobilusis telefonas. Atėjo laikas pridėti išsamų registravimą.

Pradėkime registraciją nuo liejimo proceso:

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

Įdėjus jį į kapsulę PourOperation, elgėmės išmintingai atsakomybės ir inkapsuliacijos požiūriu, tačiau dabar esame susipainioję su kintamumo principu. Be pačios operacijos, kuri gali keistis, keičiamas tampa ir pats registravimas. Pilimo operacijai turėsite atskirti ir sukurti specialų registratorių:

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

Kruopštus skaitytojas tai pastebės LogAfter, PrisijungtiPrieš и OnError taip pat gali būti keičiamas atskirai ir, analogiškai su ankstesniais veiksmais, sukurs tris klases: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

O prisiminus, kad girtuokliui yra trys operacijos, gauname devynias kirtimo klases. Dėl to visas gėrimo ratas susideda iš 14 (!!!) klasių.

Hiperbolė? Vargu ar! Žmogus beždžionė su skilimo granata išskirstys „piltuvą“ į grafiną, stiklinę, pilstymo operatorius, vandens tiekimo paslaugą, fizinį molekulių susidūrimo modelį ir kitą ketvirtį bandys išnarplioti priklausomybes. pasauliniai kintamieji. Ir patikėkite, jis nesustos.

Būtent šiuo metu daugelis daro išvadą, kad SRP yra pasakos iš rožinių karalysčių, ir nueina žaisti makaronų...

... niekada nesužinoję apie trečiojo Srp apibrėžimo egzistavimą:

„Bendros atsakomybės principas tai teigia dalykai, kurie yra panašūs į pokyčius, turėtų būti saugomi vienoje vietoje“. arba "Kokius pokyčius kartu reikėtų laikyti vienoje vietoje"

Tai yra, jei keičiame operacijos registravimą, turime jį pakeisti vienoje vietoje.

Tai labai svarbus momentas - kadangi visuose aukščiau pateiktuose SRP paaiškinimuose buvo teigiama, kad tipus reikia sutraiškyti juos gniuždant, tai yra, jie nustatė objekto dydžio „viršutinę ribą“, o dabar mes jau kalbame apie "apatinę ribą" . Kitaip tariant, SRP ne tik reikia „traiškyti trupant“, bet ir nepersistengti – „nesmulkinti susipynusių dalykų“. Tai puiki kova tarp Occamo skustuvo ir beždžionės!

Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo

Dabar geriantis turėtų jaustis geriau. Be to, kad nereikia skaidyti IPourLogger registratoriaus į tris klases, mes taip pat galime sujungti visus registratorius į vieną tipą:

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

Ir jei pridėsime ketvirtą operacijos tipą, tada jo registravimas jau paruoštas. O pačių operacijų kodas švarus ir be infrastruktūros triukšmo.

Dėl to turime 5 gėrimo problemos sprendimo klases:

  • Liejimo operacija
  • Gėrimo operacija
  • Užstrigimo operacija
  • Kirtėjas
  • Gertuvės fasadas

Kiekvienas iš jų yra atsakingas griežtai už vieną funkciją ir turi vieną priežastį keisti. Visos taisyklės, panašios į pokyčius, yra netoliese.

Realaus gyvenimo pavyzdys

Kažkada rašėme automatinio B2B kliento registravimo paslaugą. Ir 200 panašaus turinio eilučių pasirodė DIEVO metodas:

  • Eikite į 1C ir susikurkite paskyrą
  • Su šia paskyra eikite į mokėjimo modulį ir sukurkite jį ten
  • Patikrinkite, ar pagrindiniame serveryje nebuvo sukurta paskyra su tokia paskyra
  • Sukurti naują paskyrą
  • Pridėkite registracijos rezultatus mokėjimo modulyje ir 1c numerį prie registracijos rezultatų tarnybos
  • Į šią lentelę pridėkite paskyros informaciją
  • Sukurkite taško numerį šiam klientui taškų tarnyboje. Perduokite savo 1c sąskaitos numerį šiai paslaugai.

Ir šiame sąraše buvo dar apie 10 verslo operacijų su siaubingu ryšiu. Beveik visiems reikėjo paskyros objekto. Pusėje skambučių prireikė taško ID ir kliento vardo.

Po valandos pertvarkymo galėjome atskirti infrastruktūros kodą ir kai kuriuos darbo su paskyra niuansus į atskirus metodus/klases. Dievo metodas palengvino, bet liko 100 kodo eilučių, kurių tiesiog nesinorėjo išpainioti.

Tik po kelių dienų paaiškėjo, kad šio „lengvo“ metodo esmė – verslo algoritmas. Ir kad pirminis techninių specifikacijų aprašymas buvo gana sudėtingas. Ir būtent bandymas suskaidyti šį metodą į gabalus pažeis SRP, o ne atvirkščiai.

Formalizmas.

Laikas palikti savo girtuoklį ramybėje. Nusausink ašaras – kada nors prie to tikrai grįšime. Dabar įforminkime žinias iš šio straipsnio.

Formalizmas 1. SRP apibrėžimas

  1. Atskirkite elementus taip, kad kiekvienas iš jų būtų atsakingas už vieną dalyką.
  2. Atsakomybė reiškia „priežastis keistis“. Tai reiškia, kad kiekvienas elementas turi tik vieną pokyčio priežastį, atsižvelgiant į verslo logiką.
  3. Galimi verslo logikos pokyčiai. turi būti lokalizuota. Elementai, kurie keičiasi sinchroniškai, turi būti šalia.

Formalizmas 2. Būtini savitikros kriterijai.

Nemačiau pakankamai kriterijų SRP įvykdymui. Tačiau yra būtinos sąlygos:

1) Paklauskite savęs, ką daro ši klasė / metodas / modulis / paslauga. turite atsakyti į jį paprastu apibrėžimu. ( Ačiū Brightori )

paaiškinimų

Tačiau kartais labai sunku rasti paprastą apibrėžimą

2) Klaidos taisymas arba naujos funkcijos pridėjimas turi įtakos minimaliam failų / klasių skaičiui. Idealiu atveju - vienas.

paaiškinimų

Kadangi atsakomybė (už funkciją ar klaidą) yra įtraukta į vieną failą / klasę, tiksliai žinote, kur ieškoti ir ką redaguoti. Pavyzdžiui: norint pakeisti registravimo operacijų išvestį, reikės pakeisti tik registratorių. Nereikia paleisti likusios kodo dalies.

Kitas pavyzdys yra naujo vartotojo sąsajos valdiklio, panašaus į ankstesnius, pridėjimas. Jei dėl to turėsite pridėti 10 skirtingų objektų ir 15 skirtingų keitiklių, atrodo, kad persistengiate.

3) Jei keli kūrėjai dirba su skirtingomis jūsų projekto funkcijomis, tada sujungimo konflikto tikimybė, ty tikimybė, kad tą patį failą / klasę pakeis keli kūrėjai tuo pačiu metu, yra minimali.

paaiškinimų

Jeigu pridedant naują operaciją „Pilti degtinę po stalu“ reikia paveikti medkirtį, gėrimo ir pilstymo operaciją, tai atrodo, kad atsakomybės paskirstytos kreivai. Žinoma, tai ne visada įmanoma, tačiau turėtume stengtis šį skaičių sumažinti.

4) Uždavus patikslinantį klausimą apie verslo logiką (iš kūrėjo ar vadovo), einate griežtai į vieną klasę/failą ir informaciją gaunate tik iš ten.

paaiškinimų

Funkcijos, taisyklės ar algoritmai parašyti kompaktiškai, kiekviena vienoje vietoje, o ne išbarstyti vėliavėlėmis visoje kodo erdvėje.

5) Pavadinimas aiškus.

paaiškinimų

Mūsų klasė ar metodas yra atsakingas už vieną dalyką, o atsakomybė atsispindi jos pavadinime

AllManagersManagerService – greičiausiai Dievo klasė
Vietinis mokėjimas – tikriausiai ne

Formalizmas 3. Occam-first kūrimo metodika.

Projektavimo pradžioje žmogus beždžionė nežino ir nejaučia visų sprendžiamos problemos subtilybių ir gali suklysti. Klaidų galite padaryti įvairiais būdais:

  • Padarykite objektus per didelius, sujungdami skirtingas pareigas
  • Perfrazavimas padalijant vieną atsakomybę į daugybę skirtingų tipų
  • Neteisingai apibrėžti atsakomybės ribas

Svarbu atsiminti taisyklę: „geriau padaryti didelę klaidą“ arba „jei nesate tikri, neskirstykite“. Jei, pavyzdžiui, jūsų klasėje yra dvi pareigos, tai vis tiek suprantama ir gali būti padalinta į dvi dalis, minimaliai pakeitus kliento kodą. Surinkti stiklą iš stiklo šukių paprastai yra sunkiau, nes kontekstas pasklidęs keliuose failuose ir kliento kode trūksta būtinų priklausomybių.

Atėjo laikas tai pavadinti diena

SRP taikymo sritis neapsiriboja OOP ir SOLID. Tai taikoma metodams, funkcijoms, klasėms, moduliams, mikropaslaugoms ir paslaugoms. Tai taikoma tiek „figax-figax-and-prod“, tiek „raketų mokslo“ plėtrai, todėl pasaulis visur tampa šiek tiek geresnis. Jei gerai pagalvotumėte, tai beveik pagrindinis visos inžinerijos principas. Mechaninė inžinerija, valdymo sistemos ir, tiesą sakant, visos sudėtingos sistemos yra kuriamos iš komponentų, o „nepakankamas susiskaidymas“ atima dizaineriams lankstumą, „per didelis susiskaidymas“ – efektyvumą, o neteisingos ribos atima protą ir ramybę.

Vienos atsakomybės principas. Ne taip paprasta, kaip atrodo

SRP nėra išrastas gamtos ir nėra tiksliojo mokslo dalis. Tai išeina iš mūsų biologinių ir psichologinių apribojimų, tai tik būdas valdyti ir plėtoti sudėtingas sistemas, naudojant beždžionių žmogaus smegenis. Jis mums sako, kaip suskaidyti sistemą. Pradinė formuluotė reikalavo nemažos telepatijos, bet tikiuosi, kad šis straipsnis išvalo dalį dūmų.

Šaltinis: www.habr.com

Добавить комментарий