Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet

Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet Vienotas atbildības princips, kas pazīstams arī kā vienotas atbildības princips,
aka vienotas mainības princips - ārkārtīgi slidens čalis saprast un tāds nervozs jautājums programmētāja intervijā.

Mana pirmā nopietnā iepazīŔanās ar Ŕo principu notika pirmā kursa sākumā, kad jaunos un zaļos veda uz mežu, lai no kāpuriem taisītu īstus studentus.

Mežā mÅ«s sadalÄ«ja grupās pa 8-9 cilvēkiem katrā un sarÄ«kojām sacensÄ«bas - kura grupa visātrāk izdzers Ŕņabja pudeli ar nosacÄ«jumu, ka pirmais no grupas ielej degvÄ«nu glāzē, otrs izdzers, un treÅ”ajam ir uzkodas. VienÄ«ba, kas ir pabeigusi darbÄ«bu, pāriet uz grupas rindas beigām.

Gadījums, kad rindas lielums bija trīs reizes, bija laba SRP ievieŔana.

Definīcija 1. Viena atbildība.

Oficiālā vienotās atbildÄ«bas principa (SRP) definÄ«cija nosaka, ka katrai vienÄ«bai ir sava atbildÄ«ba un pastāvÄ“Å”anas iemesls, un tai ir tikai viena atbildÄ«ba.

Apsveriet objektu "Dzērājs" (Izgāzējs).
Lai īstenotu SRP principu, mēs sadalīsim pienākumus trīs daļās:

  • Viens ielej (PourOperation)
  • Viens dzēriens (DrinkUpOperation)
  • Vienam ir uzkodas (TakeBiteOperation)

Katrs no procesa dalībniekiem ir atbildīgs par vienu procesa sastāvdaļu, tas ir, viņam ir viena atomāra atbildība - dzert, ieliet vai uzkodas.

DzerŔanas bedre savukārt ir fasāde Ŕīm darbībām:

сlass Tippler {
    //...
    void Act(){
        _pourOperation.Do() // Š½Š°Š»Šøть
        _drinkUpOperation.Do() // Š²Ń‹ŠæŠøть
        _takeBiteOperation.Do() // Š·Š°ŠŗусŠøть
    }
}

Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet

Kāpēc?

Cilvēka programmētājs raksta kodu pērtiÄ·u cilvēkam, un pērtiÄ·u cilvēks ir neuzmanÄ«gs, stulbs un vienmēr steidzas. ViņŔ var turēt un saprast aptuveni 3 - 7 terminus vienlaikus.
Dzērāja gadÄ«jumā Å”ie termini ir trÄ«s. Taču, ja kodu uzrakstÄ«sim ar vienu lapu, tad tajā bÅ«s rokas, brilles, kautiņi un nebeidzami strÄ«di par politiku. Un tas viss bÅ«s vienas metodes korpusā. Esmu pārliecināts, ka esat redzējis Ŕādu kodu savā praksē. Nav humānākais pārbaudÄ«jums psihei.

No otras puses, pērtiÄ·u cilvēks ir paredzēts, lai simulētu reālās pasaules objektus savā galvā. Savā iztēlē viņŔ var tos saspiest, salikt no tiem jaunus priekÅ”metus un tāpat izjaukt. Iedomājieties veca modeļa automaŔīnu. Savā iztēlē var atvērt durvis, noskrÅ«vēt durvju apdari un ieraudzÄ«t tur logu pacelÅ”anas mehānismus, kuru iekÅ”pusē bÅ«s zobrati. Bet jÅ«s nevarat redzēt visas maŔīnas sastāvdaļas vienlaikus, vienā "sarakstā". Vismaz "pērtiÄ·u cilvēks" nevar.

Tāpēc cilvēku programmētāji sarežģītus mehānismus sadala mazāk sarežģītu un strādājoÅ”u elementu komplektā. Tomēr to var sadalÄ«t dažādi: daudzām vecām automaŔīnām gaisa vads nonāk durvÄ«s, un mÅ«sdienu automaŔīnās slēdzenes elektronikas kļūme neļauj iedarbināt dzinēju, kas var bÅ«t problēma remonta laikā.

Tagad SRP ir princips, kas izskaidro, KĀ sadalīties, tas ir, kur novilkt dalījuma līniju.

ViņŔ saka, ka ir nepiecieÅ”ams sadalÄ«ties pēc ā€œatbildÄ«basā€ sadales principa, tas ir, pēc noteiktu objektu uzdevumiem.

Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet

AtgriezÄ«simies pie dzerÅ”anas un priekÅ”rocÄ«bām, ko pērtiÄ·u cilvēks saņem sadalÄ«Å”anās laikā:

  • Kods ir kļuvis ārkārtÄ«gi skaidrs visos lÄ«meņos
  • Kodu var rakstÄ«t vairāki programmētāji vienlaikus (katrs raksta atseviŔķu elementu)
  • Automatizētā testÄ“Å”ana ir vienkārÅ”ota ā€“ jo vienkārŔāks elements, jo vieglāk to pārbaudÄ«t
  • Parādās koda kompozÄ«cija - jÅ«s varat aizstāt DrinkUpOperation uz operāciju, kurā dzērājs zem galda lej Ŕķidrumu. Vai arÄ« nomainiet ielieÅ”anas darbÄ«bu ar darbÄ«bu, kurā sajaucat vÄ«nu un Å«deni vai degvÄ«nu un alu. AtkarÄ«bā no biznesa prasÄ«bām jÅ«s varat darÄ«t visu, nepieskaroties metodes kodam Tipplers.Akts.
  • Veicot Ŕīs darbÄ«bas, jÅ«s varat salocÄ«t gluton (izmantojot tikai TakeBitOperation), Alkoholisks (lietojot tikai DrinkUpOperation tieÅ”i no pudeles) un atbilst daudzām citām uzņēmējdarbÄ«bas prasÄ«bām.

(Ak, Ŕķiet, ka tas jau ir OCP princips, un es pārkāpu Ŕīs ziņas atbildÄ«bu)

Un, protams, mīnusi:

  • Mums bÅ«s jāizveido vairāk veidu.
  • Dzērājs pirmo reizi dzer pāris stundas vēlāk, nekā citādi bÅ«tu.

Definīcija 2. Vienotā mainīgums.

Atļaujiet man, kungi! DzerÅ”anas klasei arÄ« ir viena atbildÄ«ba - tā dzer! Un vispār vārds ā€œatbildÄ«baā€ ir ārkārtÄ«gi neskaidrs jēdziens. Kāds ir atbildÄ«gs par cilvēces likteni, un kāds ir atbildÄ«gs par pingvÄ«nu izcelÅ”anu, kas tika apgāzti pie staba.

ApskatÄ«sim divus dzērāja variantus. Pirmajā, iepriekÅ” minētajā, ir trÄ«s klases - ielej, dzer un uzkodas.

Otrais ir uzrakstÄ«ts, izmantojot metodoloÄ£iju ā€œUz priekÅ”u un tikai uz priekÅ”uā€, un tajā ir visa metodes loÄ£ika RÄ«koties:

//ŠŠµ трŠ°Ń‚ŃŒŃ‚Šµ Š²Ń€ŠµŠ¼Ń  Š½Š° ŠøŠ·ŃƒŃ‡ŠµŠ½ŠøŠµ этŠ¾Š³Š¾ ŠŗŠ»Š°ŃŃŠ°. Š›ŃƒŃ‡ŃˆŠµ съŠµŃˆŃŒŃ‚Šµ ŠæŠµŃ‡ŠµŠ½ŃŒŠŗу
с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);
    }
   }
}

Abas Ŕīs klases, raugoties no ārēja novērotāja, izskatās pilnÄ«gi vienādas, un tām ir tāda pati atbildÄ«ba par ā€œdzerÅ”anuā€.

Apjukums!

Tad mēs pārejam tieÅ”saistē un uzzinām citu SRP definÄ«ciju - vienotās maināmÄ«bas principu.

SCP norāda, ka "Modulim ir viens un tikai viens iemesls mainīt". Tas ir: "Atbildība ir iemesls pārmaiņām."

(Å Ä·iet, ka puiÅ”i, kuri nāca klajā ar sākotnējo definÄ«ciju, bija pārliecināti par pērtiÄ·a cilvēka telepātiskajām spējām)

Tagad viss nostājas savās vietās. AtseviŔķi varam mainÄ«t lieÅ”anas, dzerÅ”anas un naŔķoÅ”anās procedÅ«ras, bet paŔā dzērājā varam mainÄ«t tikai darbÄ«bu secÄ«bu un sastāvu, piemēram, uzkodu pirms dzerÅ”anas izkustinot vai pievienojot grauzdiņa rādÄ«jumu.

Pieejā ā€œForward and Only Forwardā€ viss, ko var mainÄ«t, tiek mainÄ«ts tikai metodē RÄ«koties. Tas var bÅ«t lasāms un efektÄ«vs, ja ir maz loÄ£ikas un tas reti mainās, bet bieži vien tas beidzas ar Å”ausmÄ«gām metodēm, katrā 500 rindiņās, kurās ir vairāk ja-paziņojumu, nekā nepiecieÅ”ams, lai Krievija pievienotos NATO.

Definīcija 3. Izmaiņu lokalizācija.

Dzērāji bieži nesaprot, kāpēc viņi pamoduÅ”ies sveŔā dzÄ«voklÄ«, vai kur atrodas viņu mobilais tālrunis. Ir pienācis laiks pievienot detalizētu reÄ£istrÄ“Å”anu.

Sāksim reÄ£istrÄ“Å”anu ar ielieÅ”anas procesu:

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

Iekapsulējot to iekŔā PourOperation, rÄ«kojāmies gudri no atbildÄ«bas un iekapsulÄ“Å”anas viedokļa, bet tagad esam apmulsuÅ”i ar mainÄ«guma principu. Papildus paÅ”ai darbÄ«bai, kas var mainÄ«ties, maināma kļūst arÄ« pati mežizstrāde. Jums bÅ«s jāatdala un jāizveido Ä«paÅ”s reÄ£istrators lieÅ”anas operācijai:

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

RÅ«pÄ«gais lasÄ«tājs to pamanÄ«s LogAfter, PieteiktiesPirms Šø OnError var mainÄ«t arÄ« atseviŔķi, un, pēc analoÄ£ijas ar iepriekŔējām darbÄ«bām, tiks izveidotas trÄ«s klases: PourLoggerBefore, PourLoggerAfter Šø PourErrorLogger.

Un atceroties, ka dzērājam ir trÄ«s operācijas, mēs iegÅ«stam deviņas mežizstrādes klases. Rezultātā viss dzerÅ”anas aplis sastāv no 14 (!!!) nodarbÄ«bām.

Hiperbola? Diez vai! PērtiÄ·u vÄ«rs ar sadalÄ«Å”anās granātu sadalÄ«s ā€œlejniekuā€ karafe, glāzē, lieÅ”anas operatoros, Å«dens apgādes dienestā, molekulu sadursmes fiziskajā modelÄ« un nākamo ceturksni mēģinās atŔķetināt atkarÄ«bas bez globālie mainÄ«gie. Un ticiet man, viņŔ neapstāsies.

TieÅ”i Å”ajā brÄ«dÄ« daudzi nonāk pie secinājuma, ka SRP ir pasakas no rozā karaļvalstÄ«m, un dodas spēlēt nÅ«deles...

... nekad neuzzinot par treŔās Srp definīcijas esamību:

ā€œVienotās atbildÄ«bas princips nosaka to lietas, kas ir lÄ«dzÄ«gas pārmaiņām, jāglabā vienuviet". vai "Kādas izmaiņas kopā jāglabā vienuviet"

Tas ir, ja mēs mainām darbÄ«bas reÄ£istrÄ“Å”anu, tad mums tas ir jāmaina vienuviet.

Tas ir ļoti svarÄ«gs punkts - tā kā visos iepriekÅ” minētajos SRP skaidrojumos bija teikts, ka veidus bija nepiecieÅ”ams sasmalcināt, kamēr tie tika sasmalcināti, tas ir, viņi uzlika objekta izmēram ā€œaugŔējo robežuā€, un tagad mēs jau runājam par ā€œapakŔējo robežuā€ . Citiem vārdiem sakot, SRP ne tikai prasa "sasmalcināt drupināŔanas laikā", bet arÄ« nepārspÄ«lēt - "nesasmalciniet savstarpēji saistÄ«tas lietas". Å Ä« ir lielā cīņa starp Okama skuvekli un pērtiÄ·u vÄ«ru!

Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet

Tagad dzērājam vajadzētu justies labāk. Papildus tam, ka nav nepiecieÅ”ams sadalÄ«t IPourLogger reÄ£istrētāju trÄ«s klasēs, mēs varam arÄ« apvienot visus reÄ£istrētājus 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){/*..*/}
}

Un, ja pievienojam ceturto darbÄ«bas veidu, tad tā reÄ£istrÄ“Å”ana jau ir gatava. Un paÅ”u darbÄ«bu kods ir tÄ«rs un bez infrastruktÅ«ras trokŔņiem.

Rezultātā mums ir 5 nodarbÄ«bas dzerÅ”anas problēmas risināŔanai:

  • IzlieÅ”anas operācija
  • DzerÅ”anas operācija
  • IestrēgÅ”anas darbÄ«ba
  • Mežizstrādnieks
  • Dzērāju fasāde

Katrs no tiem ir stingri atbildīgs par vienu funkcionalitāti, un tam ir viens iemesls izmaiņām. Visi noteikumi, kas līdzīgi izmaiņām, atrodas netālu.

Reālās dzīves piemērs

Reiz mēs rakstÄ«jām pakalpojumu B2B klienta automātiskai reÄ£istrÄ“Å”anai. Un GOD metode parādÄ«jās 200 lÄ«dzÄ«ga satura rindām:

  • Dodieties uz 1C un izveidojiet kontu
  • Izmantojot Å”o kontu, dodieties uz maksājumu moduli un izveidojiet to tur
  • Pārbaudiet, vai galvenajā serverÄ« nav izveidots konts ar Ŕādu kontu
  • Izveidot jaunu kontu
  • Pievienojiet reÄ£istrācijas rezultātus maksājumu modulÄ« un 1c numuru reÄ£istrācijas rezultātu pakalpojumam
  • Pievienojiet Å”ai tabulai konta informāciju
  • Izveidojiet Å”im klientam punktu numuru punktu pakalpojumā. NosÅ«tiet Å”im pakalpojumam savu 1c konta numuru.

Un Å”ajā sarakstā bija vēl aptuveni 10 biznesa operācijas ar Å”ausmÄ«gu savienojamÄ«bu. Konta objekts bija vajadzÄ«gs gandrÄ«z visiem. Punkta ID un klienta vārds bija nepiecieÅ”ams pusei zvanu.

Pēc stundu ilgas pārveidoÅ”anas mēs varējām sadalÄ«t infrastruktÅ«ras kodu un dažas nianses darbā ar kontu atseviŔķās metodēs/klasēs. Dieva metode to padarÄ«ja vienkārŔāku, taču bija palikuÅ”as 100 koda rindiņas, kuras vienkārÅ”i nevēlējās tikt vaļā.

Tikai pēc dažām dienām kļuva skaidrs, ka Ŕīs ā€œvieglāsā€ metodes bÅ«tÄ«ba ir biznesa algoritms. Un ka sākotnējais tehnisko specifikāciju apraksts bija diezgan sarežģīts. Un tas ir mēģinājums sadalÄ«t Å”o metodi gabalos, kas pārkāps SRP, nevis otrādi.

Formālisms.

Ir pienācis laiks atstāt mÅ«su dzērāju vienus. Nosusiniet asaras - mēs kādreiz pie tā noteikti atgriezÄ«simies. Tagad formalizēsim Ŕī raksta zināŔanas.

Formālisms 1. SRP definīcija

  1. Atdaliet elementus tā, lai katrs no tiem būtu atbildīgs par vienu lietu.
  2. Atbildība nozīmē "iemesls mainīt". Tas nozīmē, ka katram elementam ir tikai viens iemesls izmaiņām biznesa loģikas ziņā.
  3. Iespējamas izmaiņas biznesa loģikā. jābūt lokalizētam. Elementiem, kas mainās sinhroni, jābūt tuvumā.

Formālisms 2. NepiecieÅ”amie paÅ”pārbaudes kritēriji.

Es neesmu redzējis pietiekamus kritērijus SRP izpildei. Bet ir nepiecieÅ”ami nosacÄ«jumi:

1) Pajautājiet sev, ko dara Ŕī klase/metode/modulis/pakalpojums. jums uz to jāatbild ar vienkārŔu definīciju. ( Paldies Braitori )

paskaidrojumus

Tomēr dažreiz ir ļoti grÅ«ti atrast vienkārÅ”u definÄ«ciju

2) Kļūdas laboÅ”ana vai jaunas funkcijas pievienoÅ”ana ietekmē minimālo failu/klaÅ”u skaitu. Ideālā gadÄ«jumā - viens.

paskaidrojumus

Tā kā atbildÄ«ba (par lÄ«dzekli vai kļūdu) ir ietverta vienā failā/klasē, jÅ«s precÄ«zi zināt, kur meklēt un ko rediģēt. Piemēram: reÄ£istrÄ“Å”anas operāciju izvades maiņas funkcijai bÅ«s jāmaina tikai reÄ£istrētājs. Nav nepiecieÅ”ams palaist cauri pārējam koda daļai.

Vēl viens piemērs ir jaunas lietotāja saskarnes vadÄ«klas pievienoÅ”ana, kas ir lÄ«dzÄ«ga iepriekŔējiem. Ja tas liek jums pievienot 10 dažādas entÄ«tijas un 15 dažādus pārveidotājus, Ŕķiet, ka jÅ«s pārspÄ«lējat.

3) Ja vairāki izstrādātāji strādā pie dažādām jÅ«su projekta funkcijām, tad sapludināŔanas konflikta iespējamÄ«ba, tas ir, iespējamÄ«ba, ka vienu un to paÅ”u failu/klasi mainÄ«s vairāki izstrādātāji vienlaikus, ir minimāla.

paskaidrojumus

Ja, pievienojot jaunu darbÄ«bu ā€œIeliet degvÄ«nu zem galdaā€, jāietekmē mežizstrādātājs, dzerÅ”anas un lieÅ”anas darbÄ«ba, tad izskatās, ka pienākumi sadalÄ«ti greizi. Protams, tas ne vienmēr ir iespējams, taču mums vajadzētu mēģināt Å”o skaitli samazināt.

4) Uzdodot precizējoÅ”u jautājumu par biznesa loÄ£iku (no izstrādātāja vai vadÄ«tāja), jÅ«s stingri ieejat vienā klasē/failā un saņemat informāciju tikai no turienes.

paskaidrojumus

Funkcijas, noteikumi vai algoritmi ir rakstīti kompakti, katrs vienuviet un nav izkaisīti ar karodziņiem visā koda telpā.

5) Nosaukums ir skaidrs.

paskaidrojumus

Mūsu klase vai metode ir atbildīga par vienu lietu, un atbildība atspoguļojas tās nosaukumā

AllManagersManagerService ā€” visticamāk, Dieva klase
Vietējais maksājums ā€“ iespējams, nē

Formālisms 3. Occam-first izstrādes metodoloģija.

Dizaina sākumā pērtiķu cilvēks nezina un nejūt visus risināmās problēmas smalkumus un var kļūdīties. Jūs varat kļūdīties dažādos veidos:

  • Padariet objektus pārāk lielus, apvienojot dažādus pienākumus
  • PārstrukturÄ“Å”ana, sadalot vienu atbildÄ«bu daudzos dažādos veidos
  • Nepareizi definējiet atbildÄ«bas robežas

Ir svarÄ«gi atcerēties noteikumu: "labāk ir pieļaut lielu kļūdu" vai "ja neesat pārliecināts, nesadaliet to". Ja, piemēram, jÅ«su klasē ir divi pienākumi, tad tas joprojām ir saprotams un sadalāms divās daļās, veicot minimālas izmaiņas klienta kodā. Stikla salikÅ”ana no stikla lauskas parasti ir grÅ«tāka, jo konteksts ir sadalÄ«ts vairākos failos un klienta kodā nav nepiecieÅ”amo atkarÄ«bu.

Ir pienācis laiks to nosaukt par dienu

SRP darbÄ«bas joma neaprobežojas tikai ar OOP un SOLID. Tas attiecas uz metodēm, funkcijām, klasēm, moduļiem, mikropakalpojumiem un pakalpojumiem. Tas attiecas gan uz ā€œfigax-figax-and-prodā€, gan ā€œraÄ·eÅ”u zinātnesā€ izstrādi, padarot pasauli mazliet labāku visur. Ja tā padomā, tas ir gandrÄ«z visas inženierijas pamatprincips. MaŔīnbÅ«ve, vadÄ«bas sistēmas un patieŔām visas sarežģītās sistēmas ir veidotas no komponentiem, un ā€œnepietiekama sadrumstalotÄ«baā€ atņem dizaineriem elastÄ«bu, ā€œpārmērÄ«ga sadrumstalotÄ«baā€ atņem dizaineriem efektivitāti, un nepareizas robežas atņem viņiem saprātu un sirdsmieru.

Vienas atbildības princips. Nav tik vienkārŔi, kā Ŕķiet

SRP nav dabas izgudrots un neietilpst eksaktajā zinātnē. Tas izlaužas no mÅ«su bioloÄ£iskajiem un psiholoÄ£iskajiem ierobežojumiem.Tas ir tikai veids, kā kontrolēt un attÄ«stÄ«t sarežģītas sistēmas, izmantojot pērtiÄ·a cilvēka smadzenes. ViņŔ mums stāsta, kā sadalÄ«t sistēmu. Sākotnējais formulējums prasÄ«ja diezgan daudz telepātijas, bet es ceru, ka Å”is raksts notÄ«ra daļu no dÅ«mu aizsega.

Avots: www.habr.com

Pievieno komentāru