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

Parimi i Përgjegjësisë së Vetme. Nuk është aq i thjeshtë sa duket. Principi i përgjegjësisë unike, i njohur gjithashtu si parimi i një përgjegjësie,
është një koncept shumë i vështirë për t'u kuptuar dhe një pyetje e ndërlikuar në intervistat për punë për programuesit.

Takimi im i parë serioz me këtë parim ndodhi në fillim të vitit të parë, kur të rinjtë dhe të paeksperimentuar na çuan në pyll, për të shndërruar larvat e studentëve në studentë të vërtetë.

Në pyll, na ndanë në grupe prej 8-9 personash dhe organizuam një garë - cili grup do të pijë shpejt një shishe vodka, me kushtin që personi i parë në grup të derdh vodka në gotë, i dyti ta pijë, dhe i treti të ha diçka. Njësia që realizon veprimin e saj shkon në fund të radhës së grupit.

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

Definicioni 1. Përgjegjësia unike.

Definicioni zyrtar i parimit të përgjegjësisë së vetme (SRP) thotë se çdo objekt ka një përgjegjësi dhe një arsye për ekzistencën e tij dhe kjo përgjegjësi është vetëm një.

Të shqyrtojmë objektin "Pijes" (Tippler).
Për të përmbushur parimin SRP, ndarim detyrat në tre:

  • Një derdh (PourOperation)
  • Një pi (DrinkUpOperation)
  • Një ha (TakeBiteOperation)

Çdo pjesëmarrës në proces është përgjegjës për një komponent të procesit, domethënë ka një përgjegjësi atomike - të pijë, të derdhë ose të hajë.

Tippler, nga ana e tij, është një fasadë për këto operacione:

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

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

Përse?

Njeriu-program është duke shkruar kod për njeriun-majmund, dhe njeriu-majmund është i pavëmendshëm, budalla dhe gjithmonë nxiton diku. Ai mund të mbajë dhe kuptojë rreth 3 - 7 terma në një moment.
Në rastin e tippler, këta terma janë tre. Megjithatë, nëse shkruajmë një kod si një shirit, do të ketë duar, gota, dhunë dhe debate të pafund mbi politikën. Dhe e gjithë kjo do të jetë brenda një metode. Jam i sigurt - e keni parë një kod të tillë në praktikën tuaj. Nuk është një provë e mirë për psiken.

Nga ana tjetër, njeriu-majpër ka një tendencë për të modeluar objektet e botës reale në mendjen e tij. Në imagjinatën e tij ai mund t'i përplasë ato, të krijojë objekte të reja nga to dhe po ashtu të ndajë ato. Imagjinoni një model të vjetër makine. Mund ta hapni derën në imagjinatën tuaj, të shkëputni mbulesën e derës dhe të shihni mekanizmat e ngritësve të dritareve, brenda të cilëve do të jenë ingranazhet. Por nuk mund t'i shihni të gjithë komponentët e makinës njëkohësisht, në një "listë". Të paktën "njeriu-majpër" nuk mund.

Prandaj programuesit-njerëz zbërthejnë mekanizmat e komplikuar në një grup elementësh më pak të komplikuar dhe funksionalë. Megjithatë, zbërthimi 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ë fillojë, gjë që e bën të vështirë riparimin.

Pra, SRP është një parim që shpjegon SE si të zbërthen, domethënë ku të tërhiqet linja e ndarjes..

Ai thotë se duhet të dekompozohet sipas parimit të ndarjes "të përgjegjësisë", që do të thotë sipas detyrave të objekteve të caktuara.

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

Le të kthehemi te alkoolisti dhe përfitimet që merr njeriu-maja gjatë zbërthimit:

  • Kodi është bërë maksimalisht i qartë në çdo nivel.
  • Kodi mund të shkruhet nga disa programues njëherësh (secili shkruan një element të veçantë).
  • Testimi automatizuar bëhet më i lehtë—sa më i thjeshtë të jetë elementi, aq më e lehtë është ta testosh atë.
  • Krijohet kompozueshmëri e kodit—ju mund të zëvendësoni DrinkUpOperation me një operacion, në të cilin alkoolisti derdh lëngun nën tavolinë. Ose të zëvendësoni operacionin e derdhjes me një operacion, në të cilin përzieni verën dhe ujin ose vodka dhe birrën. Në varësi të kërkesave të biznesit, ju mund të bëni gjithçka, pa e prekur kodin e metodës. Tippler.Act.
  • Nga këto operacione mund të krijoni një obez (duke përdorur vetëm TakeBitOperation), një Alkoolist (duke përdorur vetëm DrinkUpOperation direkt nga shishja) dhe të kënaqi shumë kërkesa të tjera të biznesit.

(Oh, duket se ky është tashmë parimi OCP, dhe kam shkelur përgjegjësinë e këtij posti.)

Dhe, sigurisht, disavantazhet:

  • Do t'ju duhet të krijoni më shumë lloje.
  • Alkoolisti do të pihet për herë të parë disa orë më vonë se sa mund të ishte.

Përcaktimi 2. Ndarja e vetme.

Le të themi, zotërinj! Klasa e pijes gjithashtu realizon një përgjegjësi të vetme — ajo pi! Dhe në përgjithësi, fjala "përgjegjësi" është një nocion 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ë poli.

Le të shqyrtojmë dy realizime të pijakut. E para, e përmendur më lart, përmban tre klasa – derdh, pi dhe shijoje.

E dyta, është shkruar përmes metodologjisë "Përpara dhe vetëm përpara" dhe përmban të gjithë logjikën në metodë. Akt:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
с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 këndvështrimi i një vëzhguesi të jashtëm, duken krejtësisht të ngjashme dhe realizojnë një përgjegjësi të vetme "për të pirë".

Mënyra qesharake!

Atëherë, ne hyjmë në internet dhe zbulojmë një përkufizim tjetër të SRP – Parimi i ndryshueshmërisë së vetme (Single Changeability Principle).

SCP thotë se "Moduli ka një dhe vetëm një arsye për ndryshim.". Pra, "Përgjegjësia është arsyeja për ndryshim".

(Duket se djemtë që shpikën përkufizimin origjinal ishin të sigurt në aftësitë telepatike të njeriut-majmun.)

Tani gjithçka po vendoset në vendin e vet. Mund të ndryshojmë veprimet e derdhjes, pirjes dhe shijimit ndaras, dhe në vetë pijakut mund të ndryshojmë vetëm rendin dhe përbërjen e operacioneve, për shembull, duke e zhvendosur shijimin para pirjes ose duke shtuar leximin e një tosti.

Në qasjen "Përpara dhe vetëm përpara", gjithçka që mund të ndryshohet — ndryshon vetëm në metodë. Akt. Kjo mund të jetë e lexueshme dhe efikase në rastet kur logjika është e vogël dhe ndodhi rrallë, por shpesh kjo përfundon me metoda të tmerrshme me 500 rreshta në secilën, me numrin e if-eve më të madh se sa nevojitet për hyrjen e Rusisë në NATO.

Përkufizimi 3. Lokalizimi i ndryshimeve.

Pijakët shpesh nuk kuptojnë pse janë zgjuar në një apartament të huaj, ose ku është telefoni i tyre mobil. Ka ardhur koha të shtojmë një regjistrim të detajuar.

Le të fillojmë regjistrimin 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, ne bëmë me mençuri nga pikëpamja e përgjegjësisë dhe inkapsulimit, por tani kemi një ngërç me parimin e ndryshueshmërisë. Përveç operacionit vetë, gjithashtu po bëhet e ndryshueshme logimi. Do të duhet të ndajmë dhe të krijojmë një logues special 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 po ashtu mund të ndryshojnë ndaras, dhe për të ngjashëm me veprimet e mëparshme do të krijojmë tre klasa: PourLoggerBefore, PourLoggerAfter dhe PourErrorLogger.

Dhe duke kujtuar se operacionet për pijanec janë tri — arrijmë në nëntë klasa logimi. Si rezultat, i gjithë pijaneci përbëhet nga 14 (!!!) klasa.

Hyperbola? Njëfarë, me siguri! Njeriu-majmë me granatën e dekompozimit do ta copëtojë “derdhësin” në karaf, filxhan, operatorë derdhjeje, shërbimin e ujit, modelin fizik të përplasjeve të molekulave dhe gjatë kvartalit të ardhshëm do të mundohet të zgjidhë varësitë pa variables globale. Dhe besoni — ai nuk do të ndalet.

Pikërisht në këtë moment shumë njerëz arrijnë në përfundimin se SRP — është tregime nga mbretëritë rozë, dhe shkojnë të bëjnë punë pa lidhje...

… pa e ditur kurrë 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ë ruhen në një vend”

Pra, nëse ne ndryshojmë logimin e operacionit, ne duhet ta ndryshojmë atë në një vend.

Ky është një moment shumë i rëndësishëm — pasi të gjitha shpjegimet e SRP-së, që ishin më lart, flisnin për atë se duhet të copëtohen llojet, derisa ato copëtohen, që do të thotë se vendosnin "një kufizim nga lart" mbi madhësinë e objektit, dhe tani flasim tashmë edhe për "një kufizim nga poshtë". Në fjalë të tjera, SRP jo vetëm që kërkon "të copëtohet derisa të copëtohet", por gjithashtu të mos e teprohet — "mos e copëtoni gjërat e lidhura".. Kjo është një betejë e madhe mes gjilpërës së Okamit dhe njeriut-majmë!

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

Tani pijaneci duhet të lehtësohet. Përveç faktit se nuk na nevojitet të ndajmë loguesin IPourLogger në tri klasa, ne gjithashtu mund ta bashkojmë të gjithë loguesit 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ë gati. Dhe kodi i operacioneve është i pastër dhe i liruar nga zhurma infrastrukturore.

Si rezultat, ne kemi 5 klasa për zgjidhjen e problemit të pirjes:

  • Operacioni i derdhjes
  • Operacioni i pirjes
  • Operacioni i ngrënies
  • Loguesi
  • Fasada e pirësit

Çdo njëri prej tyre përgjigjet saktësisht për një funksionalitet, ka një arsye për t'u ndryshuar. Të gjitha rregullat e ngjashme për ndryshimin janë afër njëra-tjetrës.

Shembulli nga jeta reale

Një herë ne shkruam një shërbim për regjistrimin automatik të klientëve B2B. Dhe u shfaq një metodë GOD me 200 rreshta të përmbajtjes së tillë:

  • Shko te 1C dhe hap një llogari
  • Me këtë llogari shko te moduli i pagesave dhe regjistroje atje
  • Kontrollo që një llogari me këtë numër të mos jetë krijuar në kryesore server
  • Krijo një llogari të re
  • Shtoji rezultatin e regjistrimit në modulin e pagesave dhe numrin 1C shërbimit të rezultateve të regjistrimit
  • Shtoji në këtë tabelë informacionin për llogarinë
  • Krijo një numër pikë për këtë klient në shërbimin e pikave. Dërgo në këtë shërbim numrin e llogarisë 1C.

Dhe në këtë listë kishte rreth 10 operacione biznesi me lidhje të frikshme. Objekti i llogarisë kishte nevojë për pothuajse të gjithë. Identifikuesi i pikës dhe emri i klientit ishin të nevojshme në gjysmën e thirrjeve.

Pas një orë refaktorizimi, mundëm të ndahej kodi infrastrukturore dhe disa nuanca të punës me llogarinë në metoda të veçanta / klasa. Metoda God lehtësua, por mbetën 100 rreshta kodi, të cilët nuk donin të zgjidhnin.

Vetëm pas disa ditësh erdhi kuptimi se thelbi i këtij metodologjie "në lehtësim" — është algoritmi i biznesit. Dhe se përshkrimi fillestar i TË ishe mjaft kompleks. Dhe pikërisht përpjekja për të copëtuar këtë metodë do të ishte shkelje e SRP, e jo anasjelltas.

Formalizmi.

Erdhi koha të lëmë në paqe pirësin tonë. Lani lotët — ne patjetër do të kthehemi te ai ndonjëherë. Dhe tani le të formalizojmë dijet nga ky artikull.

Formalizmi 1. Përcaktimi i SRP

  1. Ndani elementët në mënyrë që secili të jetë përgjegjës për diçka të vetëm.
  2. Përgjegjësia interpretohet si "arsye për ndryshim". Kjo do të thotë se çdo element ka vetëm një arsye për ndryshim, në terma të logjikës biznesore.
  3. Potencialet ndryshimeve të logjikës së biznesit duhet të lokalizohen. Elementët që ndryshojnë sinhronisht duhet të jenë afër njëri-tjetrit.

Formalizmi 2. Kriteret e nevojshme për vetëkontroll.

Nuk kam hasur në kritere të mjaftueshme për përmbushjen e SRP. Por ka kushte të nevojshme:

1) Shtrojeni pyetjen — çfarë bën kjo klasë/metodë/modul/shërbim. Duhet ta përgjigjeni me një definicion të thjeshtë. (faleminderit) Brightori )

shpjegime

Megjithatë, ndonjëherë është shumë e vështirë të gjendet një definicion i thjeshtë.

2) Rregullimi i ndonjë gabimi ose shtimi i një funksionaliteti të ri prek numrin minimal të skedarëve/klasave. Idealisht — një.

shpjegime

Ngjashëm me përgjegjësinë (për funksionalitetin ose gabimin) që është e inkapsuluar në një skedar/klasë, keni një ide të qartë se ku të kërkoni dhe çfarë të korrigjoni. Për shembull: funksionaliteti i ndryshimit të mënyrës së prezantimit të veprimeve do të kërkojë të ndryshohet vetëm regjistruesi. Nuk nevojitet të vraponi në të gjithë kodin tjetër.

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

3) Nëse disa zhvillues po punojnë në funksionalitete të ndryshme të projektit tuaj, probabiliteti i konflikteve të mergjit, pra probabiliteti që një i njëjtë skedar/klasë të ndryshohet nga disa zhvillues njëkohësisht — është minimal.

shpjegime

Nëse, kur shtoni operacionin e ri "Derda vodka nën tavolinë", keni nevojë të prekni regjistruesin, operacionin e pirjes dhe derdhjes — duket se përgjegjësitë janë ndarë keq. Patjetër, kjo nuk është gjithmonë e mundur, por duhet të përpiqemi të zvogëlojmë këtë tregues.

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

shpjegime

Funksionalitetet, rregullat ose algoritmet janë shkruar kompakt secila në një vend, dhe nuk janë shpërndarë me flamuj në të gjithë hapësirën e kodin.

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

shpjegime

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

AllManagersManagerService — me siguri, një klasë e Hyjit.
LocalPayment — ndoshta, jo.

Formalizmi 3. Metodologjia e zhvillimit "Okkaama-first".

Në fillim të projektimit, majmuni nuk e di dhe nuk e ndjen të gjitha nuancat e problemit që po zgjidhet dhe mund të bëjë një gabim. Mund të gabohesh në mënyra të ndryshme:

  • Të krijosh objekte shumë të mëdha duke bashkuar përgjegjësi të ndryshme.
  • Të grimcosh, duke ndarë një përgjegjësi të vetme në shumë lloje të ndryshme.
  • Të përcaktosh gabim kufijtë e përgjegjësisë.

Është e rëndësishme të mbani mend rregullin: "është më mirë të gabosh në anën e madhe", ose "nëse nuk je i sigurt — mos e copëzo". Nëse, për shembull, klasa juaj mbledh dy përgjegjësi — atëherë ajo është ende e qartë dhe mund të ndahet në dy me një ndryshim minimal në kodin e klientit. Ndërtimi i një gote nga copat e qelqit, për zakon, është më i vështirë për shkak të kontekstit të përhapur në disa skedarë dhe mungesës së varësive të nevojshme në kodin e klientit.

Është koha të mbyllemi.

Fusha e aplikimit të SRP-së nuk kufizohet në OOP dhe SOLID. Ai është i aplikueshëm për metoda, funksione, klasa, module, mikrosisteme dhe shërbime. Ai është i aplikueshëm si në zhvillimin "fiksisht-fiksisht-dhe- në-prod", ashtu edhe në "shkencën e raketave", duke e bërë botën pak më të mirë kudo. Nëse mendojmë, kjo ndoshta është një parim themelor i gjithë inxhinierisë. Inxhinieria mekanike, sistemet e menaxhimit, dhe në përgjithësi të gjitha sistemet komplekse - ndodhen nga komponentët, dhe "mos-grimcimi" privon ndërtuesit nga fleksibiliteti, "grimcimi" - nga efikasiteti, dhe kufijtë e gabuara - nga arsyeja dhe qetësia shpirtërore.

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

SRP nuk është shpikur nga natyra dhe nuk është pjesë e shkencës së saktë. Ai del nga kufizimet tona biologjike dhe psikologjike. Ky është thjesht një mënyrë për të kontrolluar dhe zhvilluar sisteme komplekse me ndihmën e trurit të njeriut-majmund. Ai na tregon se si të dekompozojmë një sistem. Formulimi fillestar kërkonte një aftësi të konsiderueshme telepatie, por shpresoj se ky artikull e ka hequr disi mjegullën.

Burimi: habr.com

Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS 🔥 Blini hosting të besueshëm për faqe interneti me mbrojtje nga DDoS, serverë VPS VDS | ProHoster