Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää

Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää Yhteisen vastuun periaate, joka tunnetaan myös yhtenäisen vastuun periaatteena,
eli tasaisen vaihtelun periaate - äärimmäisen liukas kaveri ymmärtää ja niin hermostunut kysymys ohjelmoijahaastattelussa.

Ensimmäinen vakava tutustumiseni tähän periaatteeseen tapahtui ensimmäisen vuoden alussa, kun nuoret ja vihreät vietiin metsään tekemään toukista opiskelijoita - oikeita opiskelijoita.

Metsässä meidät jaettiin 8-9 hengen ryhmiin ja pidettiin kilpailu - kumpi ryhmä juo nopeimmin pullon vodkaa, jos ryhmän ensimmäinen kaataa vodkan lasiin, toinen juo, ja kolmannella on välipala. Toimintansa suorittanut yksikkö siirtyy ryhmän jonon loppuun.

Tapaus, jossa jonon koko oli kolmen kerrannainen, oli SRP:n hyvä toteutus.

Määritelmä 1. Yksittäinen vastuu.

Single Responsibility Principle (SRP) -periaatteen virallinen määritelmä sanoo, että jokaisella kokonaisuudella on oma vastuunsa ja olemassaolonsa syynsä, ja sillä on vain yksi vastuu.

Harkitse kohdetta "juomari" (Naukkailija).
SRP-periaatteen toteuttamiseksi jaamme vastuut kolmeen:

  • Yksi kaataa (PourOperation)
  • Yksi juoma (DrinkUpOperation)
  • Yksi on välipala (TakeBiteOperation)

Jokainen prosessin osallistuja on vastuussa yhdestä prosessin osasta, eli sillä on yksi ydinvastuu - juoda, kaataa tai välipala.

Juomareikä puolestaan ​​on julkisivu näille toiminnoille:

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

Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää

Miksi?

Ihmisohjelmoija kirjoittaa koodin apinamiehelle, ja apina-ihminen on välinpitämätön, tyhmä ja aina kiireinen. Hän voi pitää sisällään ja ymmärtää noin 3-7 termiä kerralla.
Juomalaisen tapauksessa näitä termejä on kolme. Jos kuitenkin kirjoitamme koodin yhdellä arkilla, se sisältää käsiä, laseja, tappeluita ja loputtomia väittelyjä politiikasta. Ja kaikki tämä on yhden menetelmän rungossa. Olen varma, että olet nähnyt tällaisen koodin käytännössä. Ei inhimillisin testi psyykelle.

Toisaalta apinamies on suunniteltu simuloimaan todellisen maailman esineitä päässään. Hän voi mielikuvituksessaan työntää ne yhteen, koota niistä uusia esineitä ja purkaa ne samalla tavalla. Kuvittele vanha automalli. Voit mielikuvituksessasi avata oven, ruuvata oven verhoilun irti ja nähdä siellä ikkunannostimet, joiden sisällä on vaihteistot. Mutta et voi nähdä kaikkia koneen komponentteja samanaikaisesti, yhdessä "listauksessa". "Apinamies" ei ainakaan voi.

Siksi ohjelmoijat hajottavat monimutkaiset mekanismit joukoksi vähemmän monimutkaisia ​​ja toimivia elementtejä. Se voidaan kuitenkin hajottaa eri tavoin: monissa vanhoissa autoissa ilmakanava menee oveen, ja nykyaikaisissa autoissa lukkoelektroniikassa vika estää moottorin käynnistymisen, mikä voi olla ongelma korjausten aikana.

nyt SRP on periaate, joka selittää MITEN hajotetaan, eli mihin jakoviiva vedetään.

Hän sanoo, että on välttämätöntä hajota "vastuun" jaon periaatteen mukaan, eli tiettyjen esineiden tehtävien mukaan.

Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää

Palataan juomiseen ja etuihin, joita apinaihminen saa hajoamisen aikana:

  • Koodi on tullut erittäin selkeäksi kaikilla tasoilla
  • Useat ohjelmoijat voivat kirjoittaa koodin kerralla (kukin kirjoittaa erillisen elementin)
  • Automaattinen testaus on yksinkertaistettu - mitä yksinkertaisempi elementti, sitä helpompi se on testata
  • Koodin koostumus näkyy - voit korvata DrinkUpOperation operaatioon, jossa juoppo kaataa nestettä pöydän alle. Tai korvaa kaato toiminnolla, jossa sekoitat viiniä ja vettä tai vodkaa ja olutta. Liiketoiminnan vaatimuksista riippuen voit tehdä kaiken koskematta menetelmäkoodiin Tippler.Toimi.
  • Näistä toiminnoista voit taittaa ahmatin (käyttäen vain TakeBitOperation), Alkoholi (ainoastaan DrinkUpOperation suoraan pullosta) ja täyttävät monet muut liiketoiminnan vaatimukset.

(Voi, tämä näyttää jo olevan OCP-periaate, ja olen rikkonut tämän viestin vastuuta)

Ja tietysti miinukset:

  • Meidän on luotava lisää tyyppejä.
  • Juoppo juo ensimmäisen kerran pari tuntia myöhemmin kuin muuten olisi juonut.

Määritelmä 2. Unified variability.

Sallikaa minun, herrat! Juomakurssilla on myös yksi vastuu - se juo! Ja yleensä sana "vastuu" on erittäin epämääräinen käsite. Joku on vastuussa ihmiskunnan kohtalosta, ja joku on vastuussa navalla kaatuneiden pingviinien kasvattamisesta.

Tarkastellaan juojan kahta toteutusta. Ensimmäinen, edellä mainittu, sisältää kolme luokkaa - kaada, juo ja välipala.

Toinen on kirjoitettu "Eteenpäin ja vain eteenpäin" -metodologian kautta ja sisältää koko menetelmän logiikan Toimia:

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

Molemmat luokat näyttävät ulkopuolisen tarkkailijan näkökulmasta täsmälleen samalta ja jakavat saman vastuun "juomisesta".

Hämmennys!

Sitten siirrymme verkkoon ja löydämme toisen määritelmän SRP:lle - Single Changeability Principle.

SCP toteaa, että "Moduulilla on yksi ja vain yksi syy muuttaa". Eli "Vastuu on syy muutokseen."

(Näyttää siltä, ​​että alkuperäisen määritelmän keksineet kaverit olivat varmoja apinamiehen telepaattisista kyvyistä)

Nyt kaikki loksahtaa paikoilleen. Erikseen voimme muuttaa kaatamista, juomista ja napostelua, mutta itse juojassa voimme muuttaa vain toimintojen järjestystä ja koostumusta, esimerkiksi siirtämällä välipalaa ennen juomista tai lisäämällä paahtoleivän lukema.

"Eteenpäin ja vain eteenpäin" -lähestymistavassa kaikki muutettava muuttuu vain menetelmässä Toimia. Tämä voi olla luettavaa ja tehokasta, kun logiikkaa on vähän ja se muuttuu harvoin, mutta usein se päätyy kauheisiin menetelmiin, joissa kussakin on 500 riviä, joissa on enemmän jos-lauseita kuin mitä tarvitaan Venäjän liittymiseen Natoon.

Määritelmä 3. Muutosten lokalisointi.

Juojat eivät usein ymmärrä, miksi he heräsivät jonkun toisen asunnossa tai missä heidän matkapuhelimensa on. On aika lisätä yksityiskohtainen kirjaus.

Aloitetaan kirjaaminen kaatoprosessilla:

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

Kapseloimalla se sisään PourOperation, toimimme viisaasti vastuullisuuden ja kapseloitumisen näkökulmasta, mutta nyt olemme hämmentyneitä vaihtelevuuden periaatteeseen. Itse toiminnan, joka voi muuttua, lisäksi myös itse kirjaus tulee muuttuvaksi. Sinun on erotettava ja luotava erityinen loggeri kaatotoimintoa varten:

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

Huolellinen lukija huomaa sen LogAfter, LogEnnen и OnError voidaan muuttaa myös yksitellen, ja analogisesti edellisten vaiheiden kanssa luo kolme luokkaa: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

Ja muistaen, että juomarille on kolme toimenpidettä, saamme yhdeksän hakkuuluokkaa. Tämän seurauksena koko juomapiiri koostuu 14 (!!!) luokasta.

Hyperbeli? Tuskin! Hajotuskranaatilla varustettu apinamies jakaa "kaattimen" karahviin, lasiin, kaatooperaattoreihin, vesihuoltopalveluun, molekyylien törmäyksen fyysiseen malliin, ja seuraavan vuosineljänneksen aikana hän yrittää purkaa riippuvuuksia ilman globaaleja muuttujia. Ja usko minua, hän ei lopeta.

Tässä vaiheessa monet tulevat siihen johtopäätökseen, että SRP:t ovat satuja vaaleanpunaisista valtakunnista, ja lähtevät pelaamaan nuudeleita...

... oppimatta koskaan Srp:n kolmannen määritelmän olemassaolosta:

"Yhden vastuun periaate sanoo sen muutoksen kaltaiset asiat tulee säilyttää yhdessä paikassa". tai "Muutokset yhdessä kannattaa säilyttää yhdessä paikassa"

Eli jos muutamme toiminnon kirjaamista, meidän on muutettava se yhdessä paikassa.

Tämä on erittäin tärkeä seikka - koska kaikki edellä mainitut SRP:n selitykset sanoivat, että tyypit oli tarpeen murskata niitä murskattaessa, eli ne asettivat "ylärajan" objektin koolle, ja nyt puhumme jo "alarajasta". Toisin sanoen, SRP ei vaadi vain "murskaamista murskaamalla", vaan ei myöskään liioittele - "älä murskaa toisiinsa kiinnittyviä asioita". Tämä on suuri taistelu Occamin partaveitsen ja apinamiehen välillä!

Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää

Nyt juojan pitäisi tuntea olonsa paremmaksi. Sen lisäksi, että IPourLogger-loggeria ei tarvitse jakaa kolmeen luokkaan, voimme myös yhdistää kaikki loggerit yhteen tyyppiin:

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

Ja jos lisäämme neljännen toimintotyypin, sen kirjaus on jo valmis. Ja itse toimintojen koodi on puhdas ja infrastruktuurin meluton.

Tämän seurauksena meillä on 5 luokkaa juomaongelman ratkaisemiseksi:

  • Kaatotoiminto
  • Juominen toiminta
  • Tukostoiminto
  • Kirjaaja
  • Juomari julkisivu

Jokainen niistä vastaa tiukasti yhdestä toiminnosta ja sillä on yksi syy muutokseen. Kaikki muutoksen kaltaiset säännöt sijaitsevat lähellä.

Esimerkki tosielämästä

Kirjoitimme kerran palvelun b2b-asiakkaan automaattiseen rekisteröintiin. Ja GOD-menetelmä ilmestyi 200 riville samankaltaista sisältöä:

  • Siirry kohtaan 1C ja luo tili
  • Siirry tällä tilillä maksumoduuliin ja luo se siellä
  • Tarkista, ettei pääpalvelimelle ole luotu tiliä tällaisella tilillä
  • Luo uusi tili
  • Lisää rekisteröintitulokset maksumoduuliin ja 1c-numero rekisteröinnin tulospalveluun
  • Lisää tilitiedot tähän taulukkoon
  • Luo tälle asiakkaalle pistenumero pistepalvelussa. Välitä 1c-tilinumerosi tähän palveluun.

Ja tällä listalla oli noin 10 muuta liiketoimintaa, joilla oli kauheat yhteydet. Melkein kaikki tarvitsivat tiliobjektin. Pistetunnus ja asiakkaan nimi tarvittiin puolessa puheluista.

Tunnin refaktoroinnin jälkeen pystyimme erottamaan infrastruktuurikoodin ja jotkin tilityöskentelyn vivahteet erillisiin menetelmiin/luokkiin. Jumala-menetelmä helpotti sitä, mutta jäljelle jäi 100 koodiriviä, jotka eivät vain halunneet selviytyä.

Vasta muutaman päivän kuluttua kävi selväksi, että tämän "kevyen" menetelmän ydin on liiketoimintaalgoritmi. Ja teknisten eritelmien alkuperäinen kuvaus oli melko monimutkainen. Ja yritys murtaa tämä menetelmä palasiksi rikkoo SRP:tä, eikä päinvastoin.

Formalismi.

On aika jättää juomamme rauhaan. Kuivaa kyyneleesi - palaamme siihen varmasti joskus. Virallistetaan nyt tämän artikkelin tiedot.

Formalismi 1. SRP:n määritelmä

  1. Erottele elementit niin, että jokainen niistä on vastuussa yhdestä asiasta.
  2. Vastuu tarkoittaa "syytä muutokseen". Toisin sanoen jokaisella elementillä on vain yksi syy muutokseen, liikelogiikan kannalta.
  3. Mahdollisia muutoksia liiketoimintalogiikassa. on lokalisoitava. Synkronisesti muuttuvien elementtien on oltava lähellä.

Formalismi 2. Tarvittavat itsetestauksen kriteerit.

En ole nähnyt riittäviä kriteerejä SRP:n täyttämiseksi. Mutta on olemassa välttämättömät ehdot:

1) Kysy itseltäsi, mitä tämä luokka/menetelmä/moduuli/palvelu tekee. sinun on vastattava siihen yksinkertaisella määritelmällä. ( Kiitos Brightori )

selityksiä

Joskus on kuitenkin erittäin vaikea löytää yksinkertaista määritelmää

2) Virheen korjaaminen tai uuden ominaisuuden lisääminen vaikuttaa tiedostojen/luokkien vähimmäismäärään. Ihannetapauksessa - yksi.

selityksiä

Koska vastuu (ominaisuus tai virhe) on kiteytetty yhteen tiedostoon/luokkaan, tiedät tarkalleen, mistä etsiä ja mitä muokata. Esimerkiksi: lokitoimintojen tulosteen muuttaminen edellyttää vain loggerin vaihtamista. Muuta koodia ei tarvitse ajaa läpi.

Toinen esimerkki on uuden käyttöliittymäsäätimen lisääminen, joka on samanlainen kuin aikaisemmat. Jos tämä pakottaa sinut lisäämään 10 eri entiteettiä ja 15 eri muuntajaa, näyttää siltä, ​​että liioittelet.

3) Jos useat kehittäjät työskentelevät projektisi eri ominaisuuksien parissa, yhdistämisristiriidan todennäköisyys, eli todennäköisyys, että useat kehittäjät muuttavat samaa tiedostoa/luokkaa samaan aikaan, on minimaalinen.

selityksiä

Jos uutta "Kaada vodkaa pöydän alle" -toimintoa lisättäessä joutuu vaikuttamaan puunkorjujaan, juomisen ja kaatamisen toimintaan, niin näyttää siltä, ​​että vastuut on jaettu kierosti. Tämä ei tietenkään aina ole mahdollista, mutta meidän pitäisi yrittää vähentää tätä lukua.

4) Kun sinulta kysytään selvittävä kysymys liiketoimintalogiikasta (kehittäjältä tai johtajalta), siirryt tiukasti yhteen luokkaan/tiedostoon ja saat tietoa vain sieltä.

selityksiä

Ominaisuudet, säännöt tai algoritmit on kirjoitettu tiiviisti, kukin yhteen paikkaan, eikä niitä ole hajallaan lipuilla koko kooditilassa.

5) Nimitys on selkeä.

selityksiä

Luokkamme tai menetelmämme on vastuussa yhdestä asiasta, ja vastuu heijastuu sen nimessä

AllManagersManagerService - todennäköisesti God-luokka
LocalPayment - luultavasti ei

Formalismi 3. Occam-first-kehitysmetodologia.

Suunnittelun alussa apinamies ei tiedä eikä tunne kaikkia ratkaistavan ongelman hienouksia ja voi tehdä virheen. Voit tehdä virheitä eri tavoilla:

  • Tee esineistä liian suuria yhdistämällä eri vastuualueita
  • Uudelleenkehystäminen jakamalla yksi vastuu useisiin eri tyyppeihin
  • Määritä vastuun rajat väärin

On tärkeää muistaa sääntö: "on parempi tehdä suuri virhe" tai "jos et ole varma, älä jaa sitä." Jos esimerkiksi luokkasi sisältää kaksi vastuuta, se on silti ymmärrettävää ja voidaan jakaa kahteen pienellä asiakaskoodin muutoksilla. Lasin kokoaminen lasinsiruista on yleensä vaikeampaa, koska konteksti on hajautunut useisiin tiedostoihin ja koska asiakaskoodissa ei ole tarvittavia riippuvuuksia.

On aika kutsua sitä päiväksi

SRP:n laajuus ei rajoitu OOP:iin ja SOLIDiin. Se koskee menetelmiä, toimintoja, luokkia, moduuleja, mikropalveluita ja palveluita. Se koskee sekä "figax-figax-and-prod"- että "rakettitieteen" kehitystä, mikä tekee maailmasta hieman paremman kaikkialla. Jos ajattelee sitä, tämä on melkein kaiken tekniikan perusperiaate. Konetekniikka, ohjausjärjestelmät ja todellakin kaikki monimutkaiset järjestelmät rakennetaan komponenteista, ja "alifragmentointi" riistää suunnittelijoilta joustavuuden, "ylihajaantuminen" riistää suunnittelijoilta tehokkuuden ja väärät rajat riistävät heiltä järjen ja mielenrauhan.

Yhden vastuun periaate. Ei niin yksinkertaista kuin miltä se näyttää

SRP ei ole luonnon keksimä, eikä se ole osa tarkkaa tiedettä. Se murtuu biologisista ja psykologisista rajoituksistamme, ja se on vain tapa hallita ja kehittää monimutkaisia ​​järjestelmiä käyttämällä apinan miehen aivoja. Hän kertoo meille, kuinka järjestelmä hajotetaan. Alkuperäinen muotoilu vaati melkoisen määrän telepatiaa, mutta toivon, että tämä artikkeli puhdistaa osan savuverhosta.

Lähde: will.com

Lisää kommentti