مبدأ المسؤولية الفردية. ليس بهذه البساطة كما يبدو

مبدأ المسؤولية الفردية. ليس بهذه البساطة كما يبدو مبدأ المسؤولية الفردية، والمعروف أيضًا بمبدأ المسؤولية الفردية،
ويعرف أيضًا باسم مبدأ التباين الموحد - وهو رجل زلق للغاية يصعب فهمه ومثل هذا السؤال العصبي في مقابلة المبرمج.

أول تعارف جدي لي مع هذا المبدأ حدث في بداية السنة الأولى، عندما تم نقل الصغار والخضراء إلى الغابة لإخراج الطلاب من اليرقات - طلاب حقيقيين.

في الغابة، تم تقسيمنا إلى مجموعات من 8-9 أشخاص وأجرينا مسابقة - أي مجموعة ستشرب زجاجة من الفودكا بشكل أسرع، بشرط أن يصب الشخص الأول من المجموعة الفودكا في كوب، ويشربها الثاني، والثالث لديه وجبة خفيفة. تنتقل الوحدة التي أكملت عملها إلى نهاية قائمة انتظار المجموعة.

كانت الحالة التي كان فيها حجم قائمة الانتظار مضاعفًا للثلاثة بمثابة تنفيذ جيد لـ SRP.

التعريف 1. المسؤولية الفردية.

ينص التعريف الرسمي لمبدأ المسؤولية الفردية (SRP) على أن كل كيان لديه مسؤوليته الخاصة وسبب وجوده، وعليه مسؤولية واحدة فقط.

خذ بعين الاعتبار الكائن "الشارب" (مدمن الخمر).
لتنفيذ مبدأ SRP، سنقوم بتقسيم المسؤوليات إلى ثلاث:

  • يصب واحد (عملية الصب)
  • واحد يشرب (DrinkUpOperation)
  • واحد لديه وجبة خفيفة (TakeBiteOperation)

كل من المشاركين في العملية مسؤول عن عنصر واحد من العملية، أي لديه مسؤولية ذرية واحدة - للشرب أو صب أو تناول وجبة خفيفة.

وحفرة الشرب بدورها هي واجهة لهذه العمليات:

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

مبدأ المسؤولية الفردية. ليس بهذه البساطة كما يبدو

لماذا؟

يكتب المبرمج البشري التعليمات البرمجية للرجل القرد، والإنسان القرد غافل وغبي وفي عجلة من أمره دائمًا. يمكنه استيعاب وفهم حوالي 3 إلى 7 مصطلحات في وقت واحد.
وفي حالة السكير، هناك ثلاثة من هذه الشروط. ومع ذلك، إذا كتبنا الكود بورقة واحدة، فسوف يحتوي على أيدي ونظارات ومعارك وحجج لا نهاية لها حول السياسة. وكل هذا سيكون في متن طريقة واحدة. أنا متأكد من أنك رأيت مثل هذا الرمز في ممارستك. ليس الاختبار الأكثر إنسانية للنفسية.

ومن ناحية أخرى، فإن الإنسان القرد مصمم لمحاكاة أشياء العالم الحقيقي في رأسه. ويستطيع في مخيلته جمعها معًا، وتجميع أشياء جديدة منها، وتفكيكها بنفس الطريقة. تخيل سيارة موديل قديم. في مخيلتك، يمكنك فتح الباب، وفك تقليم الباب ورؤية آليات رفع النافذة، والتي سيكون بداخلها التروس. ولكن لا يمكنك رؤية جميع مكونات الجهاز في نفس الوقت، في "قائمة" واحدة. على الأقل "الرجل القرد" لا يستطيع ذلك.

ولذلك، يقوم المبرمجون البشريون بتحليل الآليات المعقدة إلى مجموعة من العناصر الأقل تعقيدًا وعملًا. ومع ذلك، يمكن أن تتحلل بطرق مختلفة: في العديد من السيارات القديمة، يدخل مجرى الهواء إلى الباب، وفي السيارات الحديثة، يمنع فشل إلكترونيات القفل المحرك من بدء التشغيل، والذي قد يكون مشكلة أثناء الإصلاحات.

وهكذا، SRP هو مبدأ يشرح كيفية التحلل، أي مكان رسم الخط الفاصل.

ويقول إنه من الضروري التحلل وفق مبدأ تقسيم «المسؤولية»، أي بحسب مهام أشياء معينة.

مبدأ المسؤولية الفردية. ليس بهذه البساطة كما يبدو

ولنعد إلى الشرب والمزايا التي يحصل عليها الرجل القرد أثناء التحلل:

  • لقد أصبح الرمز واضحًا للغاية على كل المستويات
  • يمكن كتابة الكود من قبل عدة مبرمجين في وقت واحد (كل منهم يكتب عنصر منفصل)
  • تم تبسيط الاختبار الآلي - فكلما كان العنصر أبسط، كان اختباره أسهل
  • تظهر تركيبة الكود - يمكنك استبدالها DrinkUpOperation إلى عملية يقوم فيها السكير بسكب السائل تحت الطاولة. أو استبدل عملية الصب بعملية تخلط فيها النبيذ والماء أو الفودكا والبيرة. اعتمادًا على متطلبات العمل، يمكنك القيام بكل شيء دون لمس رمز الطريقة Tipler.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}");
    }
}

بتغليفها فيه عملية الصبلقد تصرفنا بحكمة من وجهة نظر المسؤولية والتغليف، لكننا الآن في حيرة من أمرنا مع مبدأ التباين. بالإضافة إلى العملية نفسها، والتي يمكن أن تتغير، يصبح التسجيل نفسه أيضًا قابلاً للتغيير. سيتعين عليك فصل وإنشاء مسجل خاص لعملية الصب:

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, سجل قبل и خطأ يمكن أيضًا تغييرها بشكل فردي، وبالقياس على الخطوات السابقة، سيتم إنشاء ثلاث فئات: 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 تلقائيًا. وظهرت طريقة GOD لـ 200 سطر من المحتوى المماثل:

  • انتقل إلى 1C وقم بإنشاء حساب
  • باستخدام هذا الحساب، انتقل إلى وحدة الدفع وقم بإنشائه هناك
  • تأكد من عدم إنشاء حساب بهذا الحساب على الخادم الرئيسي
  • انشاء حساب جديد
  • إضافة نتائج التسجيل في وحدة الدفع ورقم 1c إلى خدمة نتائج التسجيل
  • أضف معلومات الحساب إلى هذا الجدول
  • قم بإنشاء رقم نقطة لهذا العميل في خدمة النقطة. قم بتمرير رقم حسابك 1c إلى هذه الخدمة.

وكان هناك حوالي 10 عمليات تجارية أخرى في هذه القائمة ذات اتصال سيئ. يحتاج الجميع تقريبًا إلى كائن الحساب. كانت هناك حاجة إلى معرف النقطة واسم العميل في نصف المكالمات.

بعد ساعة من إعادة البناء، تمكنا من فصل كود البنية التحتية وبعض الفروق الدقيقة في العمل مع الحساب إلى طرق/فئات منفصلة. لقد جعلت طريقة الله الأمر أسهل، ولكن كان هناك 100 سطر من التعليمات البرمجية المتبقية التي لا تريد أن يتم حلها.

فقط بعد بضعة أيام أصبح من الواضح أن جوهر هذه الطريقة "خفيفة الوزن" هو خوارزمية الأعمال. وأن الوصف الأصلي للمواصفات الفنية كان معقدًا للغاية. ومحاولة تقسيم هذا الأسلوب إلى أجزاء هي التي ستخالف برنامج التقويم الاستراتيجي، وليس العكس.

الشكلية.

حان الوقت لترك سكراننا وشأنه. جفف دموعك - حتماً سنعود إليها يوماً ما. الآن دعونا إضفاء الطابع الرسمي على المعرفة من هذه المقالة.

الشكليات 1. تعريف SRP

  1. افصل بين العناصر بحيث يكون كل منها مسؤولاً عن شيء واحد.
  2. المسؤولية تعني "سبب التغيير". أي أن كل عنصر له سبب واحد فقط للتغيير، وهو منطق الأعمال.
  3. التغييرات المحتملة في منطق الأعمال. يجب أن تكون مترجمة. العناصر التي تتغير بشكل متزامن يجب أن تكون قريبة.

الشكليات 2. معايير الاختبار الذاتي اللازمة.

لم أر معايير كافية للوفاء بخطة التقويم الاستراتيجي. ولكن هناك شروط ضرورية:

1) اسأل نفسك ما الذي تفعله هذه الفئة/الطريقة/الوحدة/الخدمة. يجب عليك الإجابة عليه بتعريف بسيط. ( شكرًا لك برايتوري )

تفسيرات

ومع ذلك، في بعض الأحيان يكون من الصعب جدًا العثور على تعريف بسيط

2) يؤثر إصلاح الخلل أو إضافة ميزة جديدة على الحد الأدنى لعدد الملفات/الفئات. من الناحية المثالية - واحد.

تفسيرات

نظرًا لأن المسؤولية (عن ميزة أو خطأ ما) تقع في ملف/فئة واحدة، فأنت تعرف بالضبط أين تبحث وما الذي يجب تحريره. على سبيل المثال: ستتطلب ميزة تغيير مخرجات عمليات التسجيل تغيير المُسجل فقط. ليست هناك حاجة لتشغيل بقية التعليمات البرمجية.

مثال آخر هو إضافة عنصر تحكم جديد لواجهة المستخدم، مشابه للعناصر السابقة. إذا أجبرك هذا على إضافة 10 كيانات مختلفة و15 محولاً مختلفًا، فيبدو أنك تبالغ في ذلك.

3) إذا كان العديد من المطورين يعملون على ميزات مختلفة لمشروعك، فإن احتمالية حدوث تعارض في الدمج، أي احتمالية تغيير نفس الملف/الفئة بواسطة العديد من المطورين في نفس الوقت، تكون ضئيلة.

تفسيرات

إذا كنت بحاجة، عند إضافة عملية جديدة "صب الفودكا تحت الطاولة"، إلى التأثير على المسجل، وعملية الشرب والصب، فيبدو أن المسؤوليات مقسمة بشكل ملتوي. وبطبيعة الحال، هذا ليس ممكنا دائما، ولكن يجب أن نحاول خفض هذا الرقم.

4) عندما يُطرح عليك سؤال توضيحي حول منطق الأعمال (من مطور أو مدير)، فإنك تنتقل بدقة إلى فئة/ملف واحد وتتلقى المعلومات من هناك فقط.

تفسيرات

تتم كتابة الميزات أو القواعد أو الخوارزميات بشكل مضغوط، كل منها في مكان واحد، ولا تكون متناثرة مع العلامات في جميع أنحاء مساحة التعليمات البرمجية.

5) التسمية واضحة.

تفسيرات

طبقتنا أو طريقتنا مسؤولة عن شيء واحد، والمسؤولية تنعكس في اسمها

AllManagersManagerService - على الأرجح فئة الله
LocalPayment - ربما لا

الشكلية 3. منهجية التطوير الأوكام الأول.

في بداية التصميم، لا يعرف الرجل القرد ولا يشعر بكل التفاصيل الدقيقة للمشكلة التي يتم حلها ويمكن أن يخطئ. يمكنك ارتكاب الأخطاء بطرق مختلفة:

  • اجعل الأشياء كبيرة جدًا من خلال دمج المسؤوليات المختلفة
  • إعادة الصياغة من خلال تقسيم المسؤولية الواحدة إلى عدة أنواع مختلفة
  • تحديد حدود المسؤولية بشكل غير صحيح

من المهم أن تتذكر القاعدة: "من الأفضل أن ترتكب خطأً كبيراً"، أو "إذا لم تكن متأكداً، فلا تقسم الأمر". على سبيل المثال، إذا كان فصلك يحتوي على مسؤوليتين، فلا يزال الأمر مفهومًا ويمكن تقسيمه إلى قسمين مع الحد الأدنى من التغييرات في رمز العميل. عادة ما يكون تجميع الزجاج من شظايا الزجاج أكثر صعوبة بسبب انتشار السياق عبر عدة ملفات وعدم وجود التبعيات الضرورية في كود العميل.

حان الوقت لنسميها اليوم

لا يقتصر نطاق SRP على OOP وSOLID. ينطبق ذلك على الأساليب والوظائف والفئات والوحدات والخدمات الصغيرة والخدمات. وهو ينطبق على كل من تطوير "figax-figax-and-prod" و"علم الصواريخ"، مما يجعل العالم أفضل قليلاً في كل مكان. إذا فكرت في الأمر، فستجد أن هذا هو المبدأ الأساسي لكل الهندسة تقريبًا. الهندسة الميكانيكية وأنظمة التحكم، بل وجميع الأنظمة المعقدة مبنية من مكونات، و"التجزئة" تحرم المصممين من المرونة، و"التجزئة المفرطة" تحرم المصممين من الكفاءة، والحدود غير الصحيحة تحرمهم من العقل وراحة البال.

مبدأ المسؤولية الفردية. ليس بهذه البساطة كما يبدو

لم يتم اختراع SRP بطبيعته وليس جزءًا من العلم الدقيق. إنه يكسر قيودنا البيولوجية والنفسية، وهو مجرد وسيلة للتحكم في الأنظمة المعقدة وتطويرها باستخدام دماغ الإنسان القرد. يخبرنا كيف نحلل النظام. تتطلب الصيغة الأصلية قدرًا لا بأس به من التخاطر، ولكنني آمل أن يزيل هذا المقال بعضًا من الستار الدخاني.

المصدر: www.habr.com

إضافة تعليق