Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket.

Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket. Principi i përgjegjësisë së vetme, gjithashtu i njohur si principi i përgjegjësisë unike,
është një koncept shumë delikat për t'u kuptuar dhe një çështje mjaft nervoze në intervistat për programuesë.

Takimi im i parë serioz me këtë princip ndodhi në fillim të vitit të parë, kur na shpërndanë në pyll për të na treguar nga larvat e studentëve - në studentë të vërtetë.

Në pyll, na ndanë në grupe prej 8-9 personash dhe vendosën një garë - cila grup do të pijë shpejt një shishe vodka, me kushtin që personi i parë nga grupa të derdhë vodkën në gotë, i dyti ta pijë, dhe i treti të hajë. Njësi e përfunduar në operacionin e tij del në fund të radhës.

Rasti kur madhësia e radhës ishte shumëfish i tre, ishte një realizim i mirë i SRP.

Definicioni 1. Pëgjegjësia unike.

Definimi zyrtar i principit të përgjegjësisë unike (SRP) thotë se çdo objekt ka përgjegjësinë e vet dhe arsye për ekzistencë, dhe kjo përgjegjësi është vetëm një.

Le të shohim objektin "Pijaku" (Tippler).
Për të zbatuar principin SRP, ndajmë detyrat në tre pjesë:

  • Njëri derdhë (PourOperation)
  • Një person pi (DrinkUpOperation)
  • Një person kafshon (TakeBiteOperation)

Çdo pjesëmarrës i procesit është përgjegjës për një komponent të procesit, që do të thotë se ka një përgjegjësi atomike — për të pirë, për të derdhur ose për të kafshuar.

Vëllai i pirjes është një fasadë për këto operacione:

class Tippler {
    //...
    void Act(){
        _pourOperation.Do() // derdh
        _drinkUpOperation.Do() // pi
        _takeBiteOperation.Do() // kafsho
    }
}

Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket.

Pse?

Një njeri-programues shkruan kod për një njeri-majmund, dhe njeri-majmundi është i pavëmendshëm, budalla dhe gjithmonë në ngut. Ai mund të mbajë dhe të kuptojë rreth 3 — 7 terma në një moment.
Në rastin e vëllait të pirjes, këta terma janë tre. Megjithatë, nëse ne shkruajmë kod në një rresht të vetëm, do të shfaqen duar, gota, përleshje dhe polemika të pafund rreth politikës. Të gjitha këto do të jenë në trup të një metode. Jam i sigurt se e keni parë atë kod në praktikën tuaj. Nuk është prova më e humanizuar për psikologjinë.

Nga ana tjetër, njeriu-majmund është i orientuar në modelimin e objekteve të botës reale në mendjen e tij. Në imagjinatën e tij, ai mund t'i përplaset ato, të krijojë objekte të reja prej tyre dhe po ashtu t'i çmontojë. Imagjinoni një model të vjetër makine. Ju mund ta hapni derën në imagjinatë, të çmontoni mbulesën e derës dhe të shihni mekanizmat e dritareve elektrike, brenda të cilëve do të jenë ingranazhet. Por nuk mund të shihni të gjithë komponentët e makinës njëkohësisht, në një 'listim'. Të paktën, 'njeriu-majmund' nuk mundet.

Prandaj, njeriun-programues e dekompozojnë mekanizmat komplekse në një grup elementësh më pak të komplikuar dhe funksionalë. Megjithatë, dekompozimi mund të bëhet në mënyra të ndryshme: në shumë makina të vjetra, kanali i ajrit del në derë, ndërsa në ato moderne, dështimi i elektronikës së çelësit nuk lejon që motori të niste, e cila merr kohë gjatë riparimit.

Pra, SRP — është një parim që shpjegon SE si të dekompozohet, dmth ku të tërhiqet linja e ndarjes..

Të dekompozohet duhet sipas parimit të ndarjes së 'përgjegjësisë', dmth sipas detyrave të objekteve të ndryshme.

Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket.

Të kthehemi te alkooli dhe përfitimet që marrin njohësit gjatë dekompozimit:

  • Kodi është bërë jashtëzakonisht i qartë në çdo nivel
  • Kodi mund të shkruhet nga disa programues në të njëjtën kohë (secili shkruan një element të veçantë)
  • Testimi automatik lehtësohet - sa më i thjeshtë të jetë elementi, aq më lehtë është ta testosh
  • Përbëhet kompozicionaliteti i kodit - mund të zëvendësohet DrinkUpOperation me një operacion, në të cilin alkooli derdh lëngun nën tryezë. Ose mund të zëvendësohet operacioni i derdhjes me një operacion, në të cilin ju përzini verën dhe ujin ose vodkën dhe birrën. Sipas kërkesave të biznesit, ju mund të bëni gjithçka, pa prekur kodin e metodës Tippler.Act.
  • Nga këto operacione mund të krijoni një alkoolik (duke përdorur vetëm TakeBitOperation), një alkoolik (duke përdorur vetëm DrinkUpOperation drejtë nga shishja) dhe të përmbushni shumë kërkesa të tjera të biznesit.

(Oi, duket se kjo është tashmë parimi OCP, dhe kam shkelur përgjegjësinë e këtij postimi)

Dhe, sigurisht, disavantazhet:

  • Do t'ju duhet të krijoni më shumë lloje.
  • Alkooliku do të pijë për herë të parë disa orë më vonë se sa do të mundej

Përkufizimi 2. Variacioni i njëjtë.

Lejo zot! Klasa e alkoolit gjithashtu ka një përgjegjësi të vetme — ajo pi! Dhe në tërësi, fjala "përgjegjësi" është një koncept jashtëzakonisht i paqartë. Disa janë përgjegjës për fatin e njerëzimit, ndërsa disa janë përgjegjës për ngritjen e pingvinëve të përmbysur në pol.

Le të shqyrtojmë dy realizime të alkoolit. E para, e përmendur më lart, përmban tre klasa — derdhje, pirje dhe mezë.

E dyta, e shkruar sipas metodologjisë "Përpara dhe vetëm përpara" dhe përmban gjithë logjikën në metodën 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);
    }
   }
}

Të dy këto klasa, nga pikëpamja e një vëzhguesi të jashtëm, duken krejtësisht të njëjta dhe kryejnë përgjegjësinë e vetme "të pinë".

Confuzion!

Atëherë, ne zhytim në internet dhe mësojmë një përkufizim tjetër të SRP — Parimi i Ndryshueshmërisë së Vetme (Single Changeability Principle).

SCP thotë se "Një modull ka një dhe vetëm një arsye për të ndryshuar".. Kjo do të thotë "Përgjegjësia është një arsye për ndryshim".

(Duket se, djemtë që shpikën përkufizimin fillestar ishin të sigurt në aftësitë telepatike të njeriut-majmë)

Tani për gjithçka vendoset në vendin e saj. Veçmas mund të ndryshohet procedura e derdhjes, pirjes dhe ushqimit, ndërsa në vetë pirjesin mund të ndryshojmë vetëm rendin dhe përbërjen e operacioneve, për shembull, duke e kaluar ushqimin para pirjes ose duke shtuar leximin e një toasti.

Në qasjen "Përpara dhe vetëm përpara", gjithçka që mund të ndryshohet - ndryshohet vetëm në metodë. Act. Kjo mund të jetë e lexueshme dhe efikase në rastet kur logjika është e vogël dhe ndryshon rrallë, por shpesh kjo përfundon me metoda të tmerrshme prej 500 rreshtash në secilën, me numrin e if-eve më të madh se sa kërkohet për anëtarësimin e Rusisë në NATO.

Përkufizimi 3. Lokalizimi i ndryshimeve.

Pira shpesh nuk e kuptojnë pse janë zgjuar në apartamentin e dikujt tjetër, ose ku është celulari i tyre. Është koha të shtohet një logim i detajuar.

Le të fillojmë logimin me procesin e derdhjes:

class PourOperation: IOperation{
    PourOperation(ILogger log /*....*/){/*...*/}
    //...
    void Do(){
        _log.Log($"Para derdhjes me {_hand} dhe {_bottle}");
        //Logjika e biznesit për derdhjen ...
        _log.Log($"Pas derdhjes me {_hand} dhe {_bottle}");
    }
}

Duke e inkapsuluar atë në PourOperation, kemi vepruar me zgjuarsi në lidhje me përgjegjësinë dhe inkapsulimin, por tani kemi një situatë të çuditshme me parimin e ndryshueshmërisë. Përveç operacionit vetë, edhe regjistrimi bëhet i ndryshueshëm. Do të nevojitet të ndajmë dhe të bëjmë një regjistrues të veçantë për operacionin e derdhjes:

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

Lexuesi i kujdesshëm do të vërejë se LogAfter, LogBefore dhe OnError igjithashtu mund të ndryshojnë veçmas, dhe me analogji me veprimet e mëparshme do të krijojmë tre klasa: PourLoggerBefore, PourLoggerAfter dhe PourErrorLogger.

Dhe duke u kujtuar se ka tre operacione për pirësit — arrijmë në nëntë klasa regjistrimi. Në fund, e gjithë struktura e pirësit përbëhet nga 14 (!!!) klasa.

Hipoteza? Padysh! Njeriu-majmune me një granatë dekompozimi do ta coptojë “derdhësin” në një karaf, gotë, operatorët e derdhjes, shërbimin e ujit, modelin fizik të goditjes së molekulave dhe në kvartalin tjetër do të përpiqet të zgjidhë varësitë pa variabla globale. Dhe besoni, ai nuk do të ndalet.

Në këtë moment, shumë arrijnë në përfundimin se SRP është histori nga mbretëritë rozë dhe largohen duke bërë lajmra…

… as që e dinë për ekzistencën e përkufizimit të tretë të Srp:

«Parimi i përgjegjësisë së vetme thotë se gjërat e ngjashme për ndryshim duhet të ruhen në një vend«. ose “Ajo që ndryshon së bashku, duhet të ruhet në një vend

Do të thotë, nëse ne e ndërojmë regjistrimin e operacionit, atëherë duhet ta ndyshojmë në një vend.

Ky është një moment shumë i rëndësishëm — pasi të gjitha shpjegimet e SRP që ishin më lart, flisnin për ndarjen e llojeve derisa ato ndahen, pra vënë një «kufizim të sipërm» mbi madhësinë e objektit, dhe tani flasim edhe për një «kufizim të poshtëm». Me fjalë të tjera, SRP kërkon jo vetëm «të coptohet derisa të coptohet», por gjithashtu të mos e teprojmë — «mos të coptojmë gjërat e lidhura». Ky është një betejë e madhe midis thikës së Ockham dhe njeriut-majmë!

Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket.

Tani, duhet t’i bëhet më e lehtë alkoolistit. Përveç faktit që nuk duhet të ndajmë logger-in IPourLogger në tri klasa, ne gjithashtu mund të bashkojmë të gjithë logger-at në një lloj:

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

Dhe nëse na shtohet një lloj i katërt operacioni, atëherë logimi për të është tashmë i gatshëm. Këtu, kodet e operacioneve janë të pastra dhe të shkarkuara nga zhurma infrastrukturore.

Si pasojë, ne kemi 5 klasa për të zgjidhur problemin e alkoolizmit:

  • Operacioni i derdhjes
  • Operacioni i alkoolizmit
  • Operacioni i ngrënies
  • Logger-i
  • Facade e alkoolizmit

Çdo njëri prej tyre është përgjegjës për një funksionalitet të vetëm, ka një shkak për ndryshim. Të gjitha rregullat për ndryshime të ngjashme janë afër njëra-tjetrës.

Shembulli nga jeta reale

Një herë, ne po shkruanim një shërbim automatik regjistrimi për klientët b2b. Dhe iu shfaq një metodë e quajtur GOD -metoda në 200 rreshta të këtij përmbajtjeje:

  • Shkoni në 1C dhe hapni një llogari
  • Me këtë llogari, shkoni te moduli i pagesës dhe hapni atje
  • Kontrolloni që llogaria me këtë llogari nuk është krijuar në kryesorin serveri
  • Krijoni një llogari të re
  • Rezultati i regjistrimit në modulën e pagesave dhe numri 1c, shto në shërbimin e rezultateve të regjistrimit.
  • Shto në këtë tabelë informacionin rreth llogarisë.
  • Krijo numrin e pikës për këtë klient në shërbimin e pikave. Dërgo në këtë shërbim numrin e llogarisë 1c.

Në këtë listë kishte edhe rreth 10 operacione biznesi me lidhje të ngushta. Objekti i llogarisë ishte i nevojshëm për shumicën. Identifikuesi i pikës dhe emri i klientit ishin të nevojshme në gjysmën e thirrjeve.

Pas një orë rifaktorimi, arritëm të ndajnë kodin infrastruktural dhe disa nuanca të punës me llogarinë në metoda/classes të veçanta. Metoda God u lehtësua, por mbetën 100 rreshta kodi që nuk donin të zgjidhnin.

Vetëm pas disa ditësh erdhi kuptimi se thelbi i kësaj metode ‘të lehtësuar’ është algoritmi i biznesit. Dhe që përshkrimi fillestar i Të Dhënave të Kerrit ishte mjaft i ndërlikuar. Dhe përpjekja për ta ndarë këtë metodë në copa do ta shkelte SRP-në, e jo anasjelltas.

Formalizmi.

Ka ardhur koha ta lëmë rehat ‘njeriun tonë të dehur’. Fshirni lotët — do të kthehemi tek ai ndonjëherë. Tani, le të formalizojmë njohuritë nga ky artikull.

Formalizmi 1. përcaktimi i SRP

  1. Dajini elementët në mënyrë që secili prej tyre të jetë përgjegjës për një gjë të vetme.
  2. Përgjegjësia interpretohet si "arsye për ndryshim". Domethënë, çdo element ka vetëm një arsye për ndryshim, në terma të logjikës së biznesit.
  3. Ndryshime potenciale të logjikës së biznesit duhet të lokalizohen. Elementet që ndryshojnë sinonimisht duhet të jenë pranë njëri-tjetrit.

Formalizmi 2. Kritere të nevojshme për vetëkontroll.

Nuk kam hasur ndonjëherë kritere të mjaftueshme për realizimin e SRP. Por ka kushte të nevojshme:

1) Porosini veten — çfarë bën ky klasë/metodë/modul/shërbim. Duhet të përgjigjeni me një përcaktim të thjeshtë. (faleminderit Brightori )

shpjegimet

Megjithatë, ndonjëherë është shumë e vështirë të gjendet një përcaktim i thjeshtë

2) Rregullimi i ndonjë defekti ose shtimi i një karakteristike të re preku numrin minimal të skedarëve/klasave. Në mënyrë ideale — një.

shpjegimet

Meqenëse përgjegjësia (për karakteristikën ose defektin) është inkapsuluar në një skedar/klasë, ju e dini saktësisht ku të kërkoni dhe çfarë të rregulloni. Për shembull: karakteristika që ndryshon formimin e logaritmit të operacioneve do të kërkojë të ndryshohet vetëm regjistruesi. Nuk është e nevojshme të shëtitni në të gjithë kodin tjetër.

Një shembull tjetër është shtimi i një kontrolli UI të ri, të ngjashëm me të mëparshmit. Nëse kjo ju detyron të shtoni 10 entitete të ndryshme dhe 15 konvertues të ndryshëm — duket se keni "tepruar".

3) Nëse disa zhvillues po punojnë mbi karakteristika të ndryshme të projektit tuaj, atëherë probabiliteti i konflikteve të bashkimeve, domethënë probabiliteti që të njëjtin skedar/klasë ta ndryshojnë disa zhvillues njëkohësisht — është minimal.

shpjegimet

Nëse kur shtoni operacionin e ri "Derdh vodka nën tavolinë" duhet të prekni regjistruesin, operacionin e pirjes dhe derdhjes — atëherë duket se përgjegjësitë janë ndarë në mënyrë të çrregullt. Sigurisht, kjo nuk është gjithmonë e mundur, por duhet të përpiqemi ta ulin këtë tregues.

4) Kur bëni një pyetje sqaruese rreth logjikës së biznesit (nga zhvilluesi ose menaxheri) ju hyni strikt në një klasë/skedar dhe merrni informacione vetëm nga aty.

shpjegimet

Veçoritë, rregullat ose algoritmet janë shkruar kompakt secila në një vend, dhe jo të shpërndara me flamuj në të gjithë hapësirën e kodit.

5) Emërtimi është i qartë.

shpjegimet

Klasë ose metoda jonë është përgjegjëse për diçka të vetme, dhe përgjegjësia reflektohet në emrin e saj.

AllManagersManagerService — gjasat janë se është një klasë e madhe.
LocalPayment — ndoshta, jo

Formalizmi 3. Metodologjia e zhvillimit «Okka-first».

Në fillim të projektimit, njeriu-majmë nuk e di dhe nuk e ndien të gjitha nuancat e detyrës së zgjidhur dhe mund të bëjë gabime. Mund të bëhen gabime në mënyra të ndryshme:

  • Të krijoshObjekte shumë të mëdha, duke bashkuar përgjegjësi të ndryshme
  • Të copëtosh, duke ndarë një përgjegjësi të vetme në shumë lloje të ndryshme
  • Të përcaktosh gabimisht kufijtë e përgjegjësisë

Është e rëndësishme të kujtohet rregulli: «më mirë të bësh gabime në anën e madhe», ose «nëse nuk je i sigurt — mos copëto». Nëse, për shembull, klasa juaj mbledh dy përgjegjësi — atëherë ajo vazhdon të jetë e kuptueshme dhe mund të ndahet në dy me ndryshime minimale në kodin klientit. Të krijosh një gotë nga copat e xhamit, zakonisht është më e vështirë për shkak të kontekstit të shtrirë në disa skedarë dhe mungesës së varësive të nevojshme në kodin klientit.

Është koha të mbyllim

Fusha e përdorimit të SRP-së nuk kufizohet vetëm në OOP dhe SOLID. Ai është i aplikueshëm për metoda, funksione, klasa, module, mikroshërbime dhe shërbime. Ai është i përshtatshëm për zhvillim si të "figaks-figaks-dhe-në-prodhim", ashtu edhe për "roketsains" duke bërë botën pak më të mirë në çdo rast. Nëse e mendojmë mirë, kjo është një nga parimet më themelore të inxhinierisë. Inxhinieria mekanike, sistemet e kontrollit dhe gjithashtu të gjitha sistemet komplekse ndërtohen nga komponente, dhe "nenndashja" privon ndërtuesit nga fleksibiliteti, "mbikëqyrja" nga efikasiteti, dhe kufijtë e gabuar nga arsyetimi dhe qetësia shpirtërore.

Principi i Përgjegjësisë së Vetme. Nuk është aq e thjeshtë sa duket.

SRP nuk është shpikje e natyrës dhe nuk është pjesë e shkencës ekzakte. Ai del nga kufizimet tona biologjike dhe psikologjike. Është thjesht një mënyrë për të kontrolluar dhe zhvilluar sisteme kompleks nga truri i njeriut-majmë. Ai na tregon se si të dekompozojmë një sistem. Formulimi fillestar kërkonte shkathtësi bastante në telepatinë, por shpresoj se ky artikull e ka hequr disi mjegullën.

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster