Ühe vastutuse põhimõte, samuti tuntud kui ühe vastutuse printsiip,
see on ka ühe muutlikkuse printsiip – äärmiselt libe mõistmiseks ja nii närviline küsimus programmeerija vestlusel.
Minu esimene tõsine kokkupuude selle põhimõttega leidis aset esimesel kursusel, kui meid noori ja rohelisi viidi metsa, et teha tudengitest – tõelised tudengid.
Metsas jagati meid rühmadesse, kus igas oli 8-9 inimest, ja korraldati võistlus – kumb rühm joob pudeli viina ära kiiremini, tingimusel et rühmast esimene inimene valab viina klaasi, teine joob, ja kolmas sööb. Toimingud lõpetanud liige läheb rühmas järjekorra lõppu.
Juhtum, kus järjekorra suurus oli kolmega jagatav, ja oli hea SRP rakendamine.
Määratlus 1. Üksik vastutus.
Ametlik määratlus ühe vastutuse printsiibist (SRP) ütleb, et igal objektile on oma vastutus ja põhjus eksisteerimiseks, ja see vastutus on vaid üks.
Vaatame objekti "Jooja" (Tippler).
SRP põhimõtte järgimiseks jagame ülesanded kolme peale:
- Üks valab (PourOperation)
- Üks joo (DrinkUpOperation)
- Üks näksib (TakeBiteOperation)
Iga osaleja protsessis vastutab ühe protsessi komponendi eest, see tähendab, et tal on üks aatomaarne vastutus — juua, valada või näksida.
Käivitus on omakorda andmete toimingute fassaad:
class Tippler {
//...
void Act(){
_pourOperation.Do() // valada
_drinkUpOperation.Do() // juua
_takeBiteOperation.Do() // näksida
}
}
Miks?
Inimene-programmeerija kirjutab koodi inimese- ahvile, kes on tähelepanematu, loll ja pidevalt kuhugi kiirustav. Ta suudab korraga meeles pidada ja mõista umbes 3-7 mõistet.
Jooja puhul on neid mõisteid kolm. Kuid kui me kirjutame koodi ühe lihtsa asjana, siis ilmuvad sinna käed, klaasid, kaklused ja lõputud vaidlused poliitika üle. Ja kõik see on ühe meetodi kehas. Olen kindel, et olete sellist koodi oma praktikas näinud. See ei ole kõige inimlikum proovikivi vaimule.
Teiselt poolt on inimeses-obeed raskusi reaalse maailma objektide modelleerimisega oma peas. Kujutluses saab ta neid kokku põrgatada, luua neist uusi objekte ja samuti neid purustada. Kujutage ette vanaaegset auto mudelit. Saate kujutluses avada ukse, eemaldada ukse polsterdamise ja näha seal aknatõstukite mehhanisme, kus sees on hammasrataste süsteem. Kuid te ei saa kõiki auto komponente korraga näha, ühes "loendis". Vähemalt "inimene-obeed" ei saa.
Seega lagundavad inimesed-programmeerijad keerulised mehhanismid vähem keerukate ja töötavate elementide kogumiteks. Kuid lagunemine võib toimuda erinevalt: paljudes vanades autodes voolab õhukanal uksest läbi, samas kui moodsates ei lase lukumehhanismi rike mootori käivitada, mis teeb remondi keeruliseks.
Nii et, SRP on põhimõte, mis selgitab KUID lagundada, st kus joon jagamiseks tõmmata..
See ütleb, et lagundada tuleks vastutuse jaotamise põhimõtte alusel, st vastavalt eri objektide ülesannetele.
Naasume joodikute ja plusside juurde, mida inimene-ahv saab detsentraliseerimise käigus:
- Kood on igal tasandil ülimalt arusaadav
- Koodi saavad kirjutada mitu programmeerijat korraga (igaühel on oma eraldi element)
- Automaatne testimine muutub lihtsamaks — mida lihtsam on element, seda lihtsam on seda testida
- Koodi kompositsioonilisus tekib — saate asendada DrinkUpOperation operatsiooni, kus joodik valab vedelikku laua alt. Või asendada valamise operatsioon operatsiooniga, kus te segate veini ja vett või viina ja õlut. Sõltuvalt ettevõtte nõudmistest saate teha kõike seda, puutumatult meetodi koodi. Tippler.Act.
- Nendest operatsioonidest võite kokku panna ahmijaa (kasutades ainult TakeBitOperation), joodiku (kasutades ainult DrinkUpOperation otse pudelist) ja rahuldada paljusid teisi ettevõtte nõudmisi.
(Oih, tundub, et see on juba OCP põhimõte ja ma rikkusin selle postituse vastutust)
Ja muidugi miinused:
- Peab looma rohkem tüüpe.
- Joodik joob esmakordselt mõne tunni hiljem, kui ta oleks võinud.
Defineering 2. Üksikasjalik muutlikkuse määratlemine.
Lugupeetud! Klass, kes joob, täidab ka ühte vastutust — ta joob! Ja üldiselt on sõna „vastutus” äärmiselt hägune mõisted. Keegi on vastutav inimkonna saatuse eest, keegi teine aga vastutab pooluselt ümber kukkunud pingviinide üles tõstmise eest.
Vaatame kaht joobunu rakendust. Esimene, nagu varem mainitud, sisaldab endas kolme klassi — valamine, joomine ja suupistetamine.
Teine on kirjutatud metoodika „Ees ja ainult edasi” 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älist vaataja silmis, näevad välja täpselt samad ning täidavad ühte vastutust „juua”.
Segadus!
Siis suundume internetti ja uurime teist SRP määratlust — Ühe Muudatuse Prinsiip (Single Changeability Principle).
SCP ütleb, et „Moodulil on üks ja ainus põhjus muutuda“. See tähendab, et „Vastutus on põhjus muutmiseks”.
(Tundub, et poisid, kes algse määratluse välja mõtlesid, uskusid inimese-ape telepaatia võimetesse)
Nüüd on kõik paigas. Protseduure, nagu valamine, joomine ja suupistete nautimine, saab muuta eraldi ning joogina saame muuta ainult toimingute järjestust ja sisu, näiteks liikudes suupiste joomise ette või lisades toosti lugemise.
Lähenemise "Ees ja ainult ette" puhul saab muuta ainult meetodi sees olevaid elemente. ActSee võib olla loetav ja efektiivne juhul, kui logika on vähene ja harva muutub, kuid sageli lõppeb see õudsete meetoditega, kus igas on 500 rida koodi ja if-e on rohkem, kui oleks vajalik Venemaa NATO-sse astumiseks.
Määratlemine 3. Muudatuste lokaliseerimine.
Joojaid üllatab tihti, miks nad ärkavad võõras korteris või kus nende mobiiltelefon on. Aeg lisada põhjalik logimine.
Alustame logimist valamisprotsessist:
class PourOperation: IOperation{
PourOperation(ILogger log /*....*/){/*...*/}
//...
void Do(){
_log.Log($"Enne valamist {_hand} ja {_bottle} abil");
//Valamise äri loogika ...
_log.Log($"Pärast valamist {_hand} ja {_bottle} abil");
}
}Inkribeerides selle PourOperation, me tegime tarku otsuseid vastutuse ja kapseldamise osas, kuid nüüd on meil probleem muutlikkuse printsiibiga. Lisaks operatsioonile, mis võib muutuda, muutub ka logimine. Peame eristama ja tegema spetsiaalse logijate operatsiooni jaoks valamise 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);
}
}
}Ahned lugeja märkab, et LogAfter, LogBefore ja OnError võivad samuti eraldi muutuda, ja eelnevate toimingute analoogiana luuakse kolm klassi: PourLoggerBefore, PourLoggerAfter ja PourErrorLogger.
Ja meenutades, et joodikule on kolm operatsiooni — saame üheksa salvestuse klassi. Lõppkokkuvõttes koosneb kogu joodik 14 (!!!) klassist.
Hüperbool? Vaevalt! Inimene-jänes dekomponatsiooni granaadiga purustab "täitja" karahviniks, klaasiks, valamisoperaatoriteks, vee esitamise teenuseks, molekulide kokkupõrke füüsiliseks mudeliks ning järgmise kvartali jooksul püüab ta lahendada sõltuvusi ilma globaalsete muutujateta. Ja uskuge mind — ta ei peatu.
Just sellel hetkel jõuavad paljud järeldusele, et SRP on roosiliste kuningriikide muinasjutud, ja lähevad nuhtlemist õmblema...
... nii ka mitte teada kolmas definitsioon Srp:
"Ühe vastutuse põhimõte ütleb, et muutmiseks sarnased asjad peavad olema ühes kohas". või "Koos muutuv asi peab olema ühes kohas.”
See tähendab, et kui me muudame tegevuste logimist, siis peame seda muutma ühes kohas.
See on väga oluline punkt — kuna kõik eespool SRP selgitused rääkisid sellest, et tuleb jagada tüüpe, kuni nad jagunevad, mis kehtestas "ülemise piiri" objekti suurusele, räägime nüüd juba ka "alampiirist". Teisisõnu, SRP ei nõua mitte ainult "jagada, kuni see jaguneb", vaid ka mitte üle pingutada — "mitte jagada omavahel seotud asju".. See on suur võitlus Ockhami nuga ja inimahviga!
Nüüd peaks purju jäämine olema lihtsam. Peale selle, et pole vaja IPourLoggerit kolme klassi jagada, saame me ka kõik logijad kokku koondada ühte tüüpi:
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 meiedele lisandub neljas operatsioonitüüp, siis selle jaoks on juba logimine valmis. Ja operatsioonide kood on puhas ja vaba infrastruktuuri müra eest.
Kokku on meil 5 klassi joogiaja ülesande lahendamiseks:
- Kalla operatsioon
- Joo operatsioon
- Söö operatsioon
- Logija
- Purjuse fassaad
Igaüks neist vastutab rangelt ühe funktsionaalsuse eest, omab ühte muutmis põhjust. Kõik sarnased muutmisreeglid asuvad üksteise kõrval.
Reaalelu näide
Kord kirjutasime automaatse b2b kliendi registreerimise teenust. Ja ilmus GOD -meetod 200 rea sarnase sisu kaupa:
- Mine 1C-sse ja ava konto
- Selle kontoga mine maksemoodulisse ja registreeri see seal
- Kontrolli, et peamine konto ei oleks sellise kontoga loodud serveril
- Loo uus konto
- Makseteen registreerimise tulemuste teenusesse ja numbri 1C lisamine.
- Lisa see teave konto kohta sellesse tabelisse.
- Loo selle kliendi jaoks punkti number punktide teenuses. Edasta sellele teenusele 1C arve number.
Ja selles loendis oli veel umbes 10 äritegevust, mis olid omavahel tihedalt seotud. Arve objekt oli vajalik enamikele. Punkti identifikaator ja kliendi nimi olid pooltel kutsungitel vajalikud.
Pärast tunni pikkust refaktoreerimist suutsime eraldada infrastruktuuri koodi ja mõned konto haldamise nüansid eraldi meetoditeks/klassideks. God meetod muutus kergemaks, kuid 100 rida koodi jäi alles, mis ei tahtnud kuidagi lahti harutada.
Ainult mõne päeva pärast tuli arusaamine, et selle «kergendatud» meetodi sisu ongi äri algoritm. Ja et algne spetsifikatsiooni kirjeldus oli üsna keeruline. Just selle meetodi tükkideks jagamise katse oleks SRP rikkumise asemel mitte vastupidi.
Formalism.
On aeg jätta meie joodik rahule. Kustuta pisarad — me naaseme temaga mingil hetkel. Aga praegu formaliseerime selle artikli teadmist.
Formalism 1. SRP määratlemine.
- Jagage elemendid niimoodi, et igaühel oleks vastutus ainult ühe asja eest.
- Vastutus tõlgendatakse kui „põhjus muudatuseks“. See tähendab, et igal elemendil on ainult üks põhjus muutuda, äri loogika mõistes.
- Potentsiaalsed muudatused äri loogikas peavad olema lokaliseeritud. Muutused, mis toimuvad sünkroonselt, peavad olema üksteise kõrval.
Formalism 2. Vajalikud enesehindamise kriteeriumid.
Ma ei ole kohanud piisavaid SRP täitmise kriteeriume. Kuid on mõned vajalikud tingimused:
1) Esitage endale küsimus — mida teeb see klass/metod/moodul/teenus? Te peate sellele vastama lihtsa määratlusega. (tänan) )
selgitused
Kuid mõnikord on väga raske leida lihtsat määratlust.
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 ühes failis/klassis, teate täpselt, kust otsida ja mida muuta. Näiteks: funktsioon operatsioonide logimise väljundi muutmiseks nõuab muutmist ainult logijalt. Teiste koodide seas ringi liikumine ei ole vajalik.
Teine näide on uue UI-juhtseadmestiku lisamine, mis sarnaneb varasematega. Kui see sunnib teid lisama 10 erinevat üksust ja 15 erinevat konverterit — tundub, et olete "ülekuulunud".
Kui mitu arendajat töötavad teie projekti erinevate funktsioonide kallal, siis on merge-konflikti tõenäosus, st et sama faili/klassiga tegeleb mitu arendajat samal ajal — minimaalne.
selgitused
Kui uue operatsiooni "Viska viina laua alla" lisamisel peate puutuma kokku logijaga, joomise ja valamise operatsiooniga — siis tundub, et vastutused on ebaühtlaselt jaotatud. Kindlasti pole see alati võimalik, kuid pingutada tuleks selle näitaja vähendamise nimel.
Selgitava küsimuse korral äri loogika kohta (arendajalt või juhilt) kaevate te täpselt ühte klassi/faili ja saate teavet ainult sealt.
selgitused
Funktsioonid, reeglid või algoritmid on kompaktsetes kirjalikes vormides, mitte igasuguseid lipukesi koodi ruumis laiali.
Nimede seadmine on arusaadav.
selgitused
Meie klass või meetod vastutab millegi ühe eest, ja vastutus peegelduku tema nimedes.
AllManagersManagerService — tõenäoliselt God-klass.
LocalPayment — tõenäoliselt mitte
Formalism 3. Arendamise meetod "Ockham-first".
Projekteerimise alguses ei tea ja ei tunne ahv inimene kõiki lahendatavaid ülesandeid ja võib eksida. Eksida saab erinevalt:
- Teha liiga suured objektid, liites erinevaid vastutusi
- Eraldada, jagades ühe vastutuse paljuks erinevateks tüüpideks
- Vääralt määrata vastutuse piire
Oluline on meeles pidada reeglit: "on parem eksida suuremalt", või "kui ei ole kindel — ära jaga". Kui näiteks teie klass hõlmab kahte vastutust — siis on see endiselt arusaadav ja seda saab jagada kaheks minimaalsete muudatustega kliendikoodis. Koorida klaassihtidest klaasi kokku on reeglina keerulisem, kuna kontekst on hajutatud mitme faili vahel ja vajalikud sõltuvused kliendikoodis puuduvad.
Aeg kokku võtta
SRP rakendusalad ei piirdu OOP ja SOLID-iga. See on kohaldatav meetoditele, funktsioonidele, klassidele, moodulitele, mikroteenustele ja teenustele. Seda saab rakendada nii 'figaks-figaks-ja-öko' kui ka 'raketiteaduse' arenduses, muutes kõikjal maailma pisut paremaks. Kui mõelda, siis on see tõeliselt põhialus kogu inseneriteaduses. Masinatootmine, juhtimissüsteemid, üldiselt kõik keerulised süsteemid — koosnevad komponentidest, ning 'ülemoodustamine' võtab konstruktoritelt paindlikkuse, 'alumoodustamine' — efektiivsuse, ja vale piirid — mõistuse ja meelerahu.
SRP pole loodud looduse poolt ega kuulu täppisteaduse hulka. See tuleneb meie bioloogilistest ja psühholoogilistest piirangutest. See on lihtsalt viis keeruliste süsteemide kontrollimiseks ja arendamiseks inimese-kontrollija abil. See õpetab meile, kuidas süsteemi dekompositsioneerida. Algne sõnastus nõudis arvestatavat telepaatiatehnikat, kuid loodan, et see artikkel hajutas veidi suitsu.
Allikas: habr.com
