Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес

Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес Бірыңғай жауапкершілік принципі, сондай-ақ бірыңғай жауапкершілік принципі ретінде белгілі,
aka біркелкі өзгергіштік принципі - түсіну өте тайғақ жігіт және бағдарламашы сұхбатында осындай жүйке сұрақ.

Менің бұл қағидамен алғашқы байыпты танысуым бірінші курстың басында, жас пен жасылдарды дернәсілден студенттер - нағыз студенттер жасау үшін орманға апарған кезде болды.

Орманда әрқайсымыз 8-9 адамнан топтарға бөлініп, жарысты – қай топ бір бөтелке арақты жылдам ішеді, егер топтан бірінші адам стақанға арақ құйса, екіншісі ішсе, ал үшіншісі тамақ ішеді. Жұмысын аяқтаған бөлімше топ кезегінің соңына қарай жылжиды.

Кезектің өлшемі үшке еселенген жағдай SRP-тің жақсы орындалуы болды.

Анықтама 1. Бірыңғай жауапкершілік.

Бірыңғай жауапкершілік қағидатының (SRP) ресми анықтамасында әрбір субъектінің өз жауапкершілігі мен өмір сүру себебі бар және оның бір ғана жауапкершілігі бар деп көрсетілген.

«Ішуші» нысанын қарастырайық (Типплер).
SRP принципін жүзеге асыру үшін біз жауапкершілікті үшке бөлеміз:

  • Бір құйылады (PourOperation)
  • Бір ішеді (DrinkUpOperation)
  • Біреуінде тамақ бар (TakeBiteOperation)

Процеске қатысушылардың әрқайсысы процестің бір құрамдас бөлігі үшін жауап береді, яғни бір атомдық жауапкершілікке ие - ішу, құю немесе тағамдар.

Ішетін тесік, өз кезегінде, бұл операциялар үшін қасбет болып табылады:

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

Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес

Неліктен?

Адамның программисті маймыл адамға код жазады, ал маймыл адам зейінсіз, ақымақ және әрқашан асығыс. Ол бір уақытта шамамен 3 - 7 терминді ұстанып, түсіне алады.
Ішімдікке салынған жағдайда бұл терминдердің үшеуі бар. Алайда, егер кодты бір парақпен жазсақ, онда қолдар, көзілдірік, төбелес және саясат туралы бітпейтін айтыстар болады. Және мұның бәрі бір әдістің денесінде болады. Сіз өзіңіздің тәжірибеңізде осындай кодты көргеніңізге сенімдімін. Психика үшін ең адамгершілік сынақ емес.

Екінші жағынан, маймыл адам өз басындағы нақты әлем объектілерін имитациялауға арналған. Ол өз қиялында оларды бір-біріне итеріп, олардан жаңа заттарды құрастырып, бірдей етіп бөлшектей алады. Ескі үлгідегі көлікті елестетіңіз. Сіздің қиялыңызда сіз есікті ашып, есіктің әрлеуін бұрап, терезені көтеру механизмдерін көре аласыз, олардың ішінде тісті берілістер болады. Бірақ машинаның барлық бөлшектерін бір уақытта, бір «листингтен» көре алмайсыз. Кем дегенде, «маймыл адам» мүмкін емес.

Сондықтан адам бағдарламашылары күрделі механизмдерді күрделі емес және жұмыс істейтін элементтердің жиынтығына ыдыратады. Дегенмен, ол әртүрлі жолмен ыдырауы мүмкін: көптеген ескі автомобильдерде ауа құбыры есікке кіреді, ал қазіргі заманғы автомобильдерде құлыптау электроникасындағы ақаулық қозғалтқышты іске қосуға кедергі келтіреді, бұл жөндеу кезінде қиындық тудыруы мүмкін.

Солай, SRP - ҚАЛАЙ ыдырау керектігін, яғни бөлу сызығын қайда салу керектігін түсіндіретін принцип..

Ол «жауапкершілікті» бөлу принципі бойынша, яғни белгілі бір объектілердің міндеттеріне қарай ыдырау керектігін айтады.

Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес

Ішуге және маймыл адамның ыдырау кезінде алатын артықшылықтарына оралайық:

  • Код барлық деңгейде өте анық болды
  • Кодты бірден бірнеше бағдарламашы жаза алады (әрқайсысы бөлек элемент жазады)
  • Автоматтандырылған тестілеу жеңілдетілген - элемент неғұрлым қарапайым болса, соғұрлым оны тексеру оңайырақ
  • Кодтың композициясы пайда болады - ауыстыруға болады DrinkUpOperation мас адамның үстелдің астына сұйықтық құйған операциясына. Немесе құю операциясын шарап пен суды немесе арақ пен сыраны араластыратын операциямен ауыстырыңыз. Іскерлік талаптарға байланысты әдіс кодына қол тигізбестен бәрін жасауға болады Tippler.Act.
  • Осы операциялардан сіз ашкөзді бүктей аласыз (тек TakeBitOperation), Алкогольдік (тек пайдалану DrinkUpOperation тікелей бөтелкеден) және басқа да көптеген бизнес талаптарын қанағаттандырады.

(О, бұл қазірдің өзінде OCP принципі сияқты, мен бұл жазбаның жауапкершілігін бұздым)

Және, әрине, кемшіліктер:

  • Бізге көбірек түрлер жасау керек.
  • Маскүнем бірінші рет ішкеннен бірнеше сағат кешіктіріп ішеді.

Анықтама 2. Бірыңғай өзгергіштік.

Маған рұқсат етіңіздер, мырзалар! Ішетін сыныптың да бір жауапкершілігі бар – ол ішеді! Жалпы, «жауапкершілік» сөзі өте түсініксіз ұғым. Біреу адамзаттың тағдырына жауапты, ал біреу бағанада төңкерілген пингвиндерді өсіруге жауапты.

Ішетін адамның екі іске асыруын қарастырайық. Біріншісі, жоғарыда аталған, үш сыныпты қамтиды - құю, ішу және тағамдар.

Екіншісі «Алға және тек алға» әдістемесі арқылы жазылған және әдістегі барлық логиканы қамтиды Заң:

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

Бұл екі сыныптың сырттан бақылаушы көзқарасы бойынша, бірдей көрінеді және «ішу» жауапкершілігін бөліседі.

Шатасу!

Содан кейін біз желіге кіріп, SRP-нің басқа анықтамасын - Бірыңғай өзгеру принципін білеміз.

SCP «Модульдің өзгертуге бір ғана себебі бар«. Яғни, «Жауапкершілік – өзгерістердің себебі».

(Бастапқы анықтаманы ойлап тапқан жігіттер маймыл адамның телепатиялық қабілетіне сенімді болған сияқты)

Қазір бәрі өз орнына келеді. Бөлек, біз құю, ішу және тіскебасар процедураларын өзгерте аламыз, бірақ ішетін адамның өзінде біз операциялардың реті мен құрамын ғана өзгерте аламыз, мысалы, ішу алдында тіскебасарды жылжыту немесе тост оқуын қосу арқылы.

«Алға және тек алға» тәсілінде өзгертуге болатын барлық нәрсе тек әдісте ғана өзгертіледі Заң. Бұл логика аз болған кезде оқылатын және тиімді болуы мүмкін және ол сирек өзгереді, бірақ көбінесе ол әрқайсысы 500 жолдан тұратын қорқынышты әдістермен аяқталады және Ресейдің НАТО-ға қосылуы үшін талап етілетіннен көп if-мәлімдемелерімен аяқталады.

Анықтама 3. Өзгерістерді локализациялау.

Ішімдік ішетіндер көбінесе біреудің пәтерінде неліктен оянғанын немесе ұялы телефонының қайда екенін түсінбейді. Егжей-тегжейлі журналды қосу уақыты келді.

Тіркеуді құю процесінен бастайық:

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

Оны инкапсуляциялау арқылы PourOperation, біз жауапкершілік пен инкапсуляция тұрғысынан ақылмен әрекет еттік, бірақ қазір өзгергіштік принципімен шатастырдық. Өзгеруі мүмкін операцияның өзінен басқа, журналдың өзі де өзгермелі болады. Құю операциясы үшін арнайы тіркеушіні бөліп, жасау керек:

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

Мұқият оқырман мұны байқайды LogAfter, LogBefor и OnError жеке өзгертуге болады және алдыңғы қадамдарға ұқсас үш сыныпты жасайды: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

Ал ішетін адамға үш операция бар екенін еске түсірсек, тоғыз ағаш кесу сабағын аламыз. Нәтижесінде бүкіл ауызсу үйірмесі 14 (!!!) сыныптан тұрады.

Гипербола? Әрең! Ыдырау гранатасы бар маймыл адам «құйғышты» графинге, стақанға, құю операторларына, сумен жабдықтау қызметіне, молекулалардың соқтығысудың физикалық моделіне бөледі және келесі тоқсанда ол тәуелділіктерді сызбасыз шешуге тырысады. жаһандық айнымалылар. Маған сеніңіз, ол тоқтамайды.

Дәл осы кезде көптеген адамдар SRP қызғылт патшалықтардың ертегілері деген қорытындыға келеді және кеспе ойнауға кетеді ...

... Srp үшінші анықтамасының бар екендігі туралы ешқашан білместен:

«Бірыңғай жауапкершілік қағидасында бұл айтылған өзгертуге ұқсас нәрселер бір жерде сақталуы керек«. немесе «Қандай өзгерістер бірге сақталуы керек бір жерде«

Яғни, егер біз операцияның журналын өзгертсек, онда оны бір жерде өзгертуіміз керек.

Бұл өте маңызды сәт - өйткені жоғарыда келтірілген SRP-нің барлық түсіндірмелерінде түрлерді ұсақтау кезінде оларды ұсақтау қажет екендігі айтылған, яғни олар нысанның өлшеміне «жоғарғы шек» қойды, ал қазір біз қазірдің өзінде «төменгі шек» туралы айтып отырмыз. Басқа сөздермен айтқанда, SRP «ұсақтау кезінде ұсақтауды» ғана емес, сонымен бірге оны асыра алмауды да талап етеді - «бір-біріне жабысатын заттарды сындырмаңыз». Бұл Оккамның ұстарасы мен маймыл адам арасындағы ұлы шайқас!

Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес

Енді ішетін адам өзін жақсы сезінуі керек. IPourLogger тіркеушісін үш сыныпқа бөлудің қажеті жоқтығынан басқа, біз барлық тіркеушілерді бір түрге біріктіре аламыз:

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

Ал егер операцияның төртінші түрін қоссақ, онда ол үшін журналдар қазірдің өзінде дайын. Ал операциялардың кодының өзі таза және инфрақұрылымдық шусыз.

Нәтижесінде бізде ауызсу мәселесін шешуге арналған 5 сынып бар:

  • Құю операциясы
  • Ішімдік операциясы
  • Кептелу операциясы
  • Жазушы
  • Ішетін қасбет

Олардың әрқайсысы бір функцияға қатаң жауап береді және өзгертудің бір себебі бар. Өзгерістерге ұқсас барлық ережелер жақын жерде орналасқан.

Шынайы өмірден мысал

Біз бір кездері b2b клиентін автоматты түрде тіркеу қызметін жазғанбыз. Ұқсас мазмұндағы 200 жол үшін GOD әдісі пайда болды:

  • 1С-ге өтіп, тіркелгі жасаңыз
  • Осы тіркелгімен төлем модуліне өтіп, оны сол жерде жасаңыз
  • Негізгі серверде мұндай тіркелгісі бар тіркелгі жасалмағанын тексеріңіз
  • Жаңа тіркелгі жасаңыз
  • Төлем модуліндегі тіркеу нәтижелерін және тіркеу нәтижелері қызметіне 1c нөмірін қосыңыз
  • Осы кестеге тіркелгі ақпаратын қосыңыз
  • Нүкте қызметінде осы клиент үшін нүкте нөмірін жасаңыз. 1c шот нөміріңізді осы қызметке жіберіңіз.

Бұл тізімде қорқынышты байланысы бар тағы 10-ға жуық бизнес операциясы болды. Барлығына дерлік тіркелгі нысаны қажет болды. Нүкте идентификаторы мен клиенттің аты қоңыраулардың жартысына қажет болды.

Бір сағаттық рефакторингтен кейін біз инфрақұрылым кодын және есептік жазбамен жұмыс істеудің кейбір нюанстарын бөлек әдістерге/сыныптарға бөле алдық. Құдай әдісі оны жеңілдетті, бірақ 100 код жолы қалды, олар жай ғана шешілгісі келмеді.

Бірнеше күннен кейін ғана бұл «жеңіл» әдістің мәні бизнес алгоритмі екені белгілі болды. Және техникалық сипаттамалардың бастапқы сипаттамасы өте күрделі болды. Және бұл әдісті бөлшектеу әрекеті SRP-ні бұзады, керісінше емес.

Формализм.

Біздің масымызды жалғыз қалдыратын кез келді. Көз жасыңызды құрғатыңыз - біз оған бір күні міндетті түрде ораламыз. Енді осы мақаладан алған білімімізді бекітейік.

Формализм 1. SRP анықтамасы

  1. Элементтерді әрқайсысы бір нәрсеге жауапты болатындай етіп бөліңіз.
  2. Жауапкершілік «өзгерту себебін» білдіреді. Яғни, әрбір элементтің бизнес логикасы тұрғысынан өзгертуге бір ғана себебі бар.
  3. Бизнес логикасына ықтимал өзгерістер. локализациялануы керек. Синхронды түрде өзгеретін элементтер жақын жерде болуы керек.

Формализм 2. Қажетті өзін-өзі тексеру критерийлері.

Мен SRP орындау үшін жеткілікті критерийлерді көрмедім. Бірақ қажетті шарттар бар:

1) Бұл сынып/әдіс/модуль/қызмет не істейтінін өзіңізден сұраңыз. сіз оған қарапайым анықтамамен жауап беруіңіз керек. ( Рақмет сізге Брайтори )

түсіндірмелер

Дегенмен, кейде қарапайым анықтаманы табу өте қиын

2) Қатені түзету немесе жаңа мүмкіндікті қосу файлдардың/сыныптардың ең аз санына әсер етеді. Ең дұрысы - бір.

түсіндірмелер

Жауапкершілік (мүмкіндік немесе қате үшін) бір файлда/сыныпта инкапсуляцияланғандықтан, сіз қайдан іздеу керектігін және нені өңдеу керектігін нақты білесіз. Мысалы: тіркеу операцияларының шығысын өзгерту мүмкіндігі тек тіркеушіні өзгертуді қажет етеді. Қалған кодты орындаудың қажеті жоқ.

Тағы бір мысал - алдыңғыларға ұқсас жаңа UI басқару элементін қосу. Егер бұл сізге 10 түрлі нысанды және 15 түрлі түрлендіргішті қосуға мәжбүр етсе, сіз оны асыра орындаған сияқтысыз.

3) Егер бірнеше әзірлеушілер жобаңыздың әртүрлі мүмкіндіктерімен жұмыс істеп жатса, онда біріктіру қақтығыстарының ықтималдығы, яғни бір файлды/сыныпты бір уақытта бірнеше әзірлеушілер өзгерту ықтималдығы аз.

түсіндірмелер

Егер «Үстел астына арақ құй» деген жаңа операцияны қосқанда, логерге, ішу және құю операциясына әсер ету керек болса, онда жауапкершілік қисық бөлінген сияқты. Әрине, бұл әрқашан мүмкін емес, бірақ біз бұл көрсеткішті азайтуға тырысуымыз керек.

4) Бизнес логикасы туралы нақтылайтын сұрақ қойылғанда (әзірлеушіден немесе менеджерден) сіз қатаң түрде бір сыныпқа/файлға кіріп, ақпаратты тек сол жерден аласыз.

түсіндірмелер

Мүмкіндіктер, ережелер немесе алгоритмдер ықшам, әрқайсысы бір жерде жазылған және код кеңістігінде жалаушалармен шашыраңқы емес.

5) Атауы түсінікті.

түсіндірмелер

Біздің сынып немесе әдіс бір нәрсеге жауапты, ал жауапкершілік оның атауында көрінеді

AllManagersManagerService - ең алдымен Құдай класы
LocalPayment - мүмкін емес

Формализм 3. Оккам-бірінші даму әдістемесі.

Дизайндың басында маймыл адам шешілетін мәселенің барлық нәзіктіктерін білмейді және сезбейді және қателесуі мүмкін. Сіз әртүрлі жолдармен қателіктер жібере аласыз:

  • Әртүрлі жауапкершіліктерді біріктіру арқылы нысандарды тым үлкен етіп жасаңыз
  • Бір жауапкершілікті көптеген әртүрлі түрлерге бөлу арқылы қайта құру
  • Жауапкершілік шекарасын дұрыс белгілемеу

Ережені есте сақтау маңызды: «үлкен қателік жасаған дұрыс» немесе «егер сенбесеңіз, оны бөлмеңіз». Мысалы, сіздің сыныпта екі жауапкершілік болса, ол әлі де түсінікті және клиент кодындағы ең аз өзгерістермен екіге бөлуге болады. Шыны сынықтарынан әйнекті құрастыру әдетте контексттің бірнеше файлдарға таралуына және клиент кодында қажетті тәуелділіктердің болмауына байланысты қиынырақ болады.

Оны бір күн деп атайтын уақыт келді

SRP ауқымы OOP және SOLID-пен шектелмейді. Ол әдістерге, функцияларға, сыныптарға, модульдерге, микросервистерге және қызметтерге қолданылады. Ол «figax-figax-and-prod» және «ракета-ғылым» дамуына қатысты, бұл әлемді барлық жерде сәл жақсырақ етеді. Егер сіз бұл туралы ойласаңыз, бұл барлық инженерияның негізгі принципі дерлік. Машина жасау, басқару жүйелері және шынында да барлық күрделі жүйелер құрамдас бөліктерден құрастырылған және «аз фрагментация» конструкторларды икемділіктен айырады, «шамадан тыс фрагментация» дизайнерлерді тиімділіктен айырады, ал дұрыс емес шекаралар оларды ақыл мен тыныштықтан айырады.

Бірыңғай жауапкершілік қағидасы. Бұл көрінетіндей қарапайым емес

SRP табиғаттан ойлап табылмаған және нақты ғылымның бөлігі емес. Бұл біздің биологиялық және психологиялық шектеулерден шығады.Бұл маймыл-адамның миын пайдаланып күрделі жүйелерді басқару және дамытудың бір жолы ғана. Ол бізге жүйені қалай ыдырату керектігін айтады. Түпнұсқалық тұжырым телепатияның жеткілікті мөлшерін талап етті, бірақ бұл мақала түтін экранының біразын тазартады деп үміттенемін.

Ақпарат көзі: www.habr.com

пікір қалдыру