اصل مسئولیت واحد به آن سادگی که به نظر می رسد نیست

اصل مسئولیت واحد به آن سادگی که به نظر می رسد نیست اصل مسئولیت واحد که به عنوان اصل مسئولیت واحد نیز شناخته می شود.
با نام مستعار اصل تغییرپذیری یکنواخت - یک مرد بسیار لغزنده برای درک و چنین سوال عصبی در مصاحبه برنامه نویس.

اولین آشنایی جدی من با این اصل در ابتدای سال اول اتفاق افتاد، زمانی که جوان ها و سبزها را به جنگل بردند تا از لاروها - دانش آموزان واقعی - شاگردان بسازند.

در جنگل، ما به گروه های 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. تنوع یکپارچه.

اجازه بدهید آقایان! کلاس نوشیدن نیز یک مسئولیت دارد - می نوشد! و به طور کلی، کلمه "مسئولیت" یک مفهوم بسیار مبهم است. یکی مسئول سرنوشت بشریت است و دیگری مسئول بزرگ کردن پنگوئن هایی است که در قطب واژگون شده اند.

بیایید دو اجرای آبخوری را در نظر بگیریم. اولین مورد، که در بالا ذکر شد، شامل سه کلاس است - ریختن، نوشیدنی و میان وعده.

دومی از طریق متدولوژی "Forward and Only Forward" نوشته شده است و شامل تمام منطق روش است عمل:

//Не тратьте время  на изучение этого класса. Лучше съешьте печеньку
с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 خطی ختم می شود، با بیانیه های اگر بیش از آنچه برای پیوستن روسیه به ناتو لازم است.

تعریف 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, LogBefore и یک خطا همچنین می تواند به صورت جداگانه تغییر کند و به قیاس با مراحل قبلی، سه کلاس ایجاد می کند: 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 خط محتوای مشابه ظاهر شد:

  • به 1C بروید و یک حساب کاربری ایجاد کنید
  • با این حساب به ماژول پرداخت بروید و آن را در آنجا ایجاد کنید
  • بررسی کنید که حسابی با چنین حساب کاربری در سرور اصلی ایجاد نشده باشد
  • ایجاد یک حساب کاربری جدید
  • نتایج ثبت نام را در ماژول پرداخت و عدد 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" و "rocket-science" اعمال می شود و جهان را در همه جا کمی بهتر می کند. اگر در مورد آن فکر کنید، این تقریباً اصل اساسی تمام مهندسی است. مهندسی مکانیک، سیستم‌های کنترل و در واقع همه سیستم‌های پیچیده از اجزای سازنده ساخته شده‌اند، و «زیر تکه‌شکل‌سازی» انعطاف‌پذیری را از طراحان سلب می‌کند، «تجزیه بیش از حد» کارایی را از طراحان سلب می‌کند و مرزهای نادرست عقل و آرامش را از آنها سلب می‌کند.

اصل مسئولیت واحد به آن سادگی که به نظر می رسد نیست

SRP توسط طبیعت اختراع نشده است و بخشی از علم دقیق نیست. از محدودیت های بیولوژیکی و روانی ما خارج می شود و تنها راهی برای کنترل و توسعه سیستم های پیچیده با استفاده از مغز انسان میمون است. او به ما می گوید که چگونه یک سیستم را تجزیه کنیم. فرمول اولیه نیاز به مقدار مناسبی از تله پاتی داشت، اما امیدوارم این مقاله برخی از پرده های دود را پاک کند.

منبع: www.habr.com

اضافه کردن نظر