Wednesday, January 17, 2018

هندسة البرمجيات بالهجايص (4): مبدأ لسكوف للاستبدال

[مش عارف مصدر الصورة إيه، بس هي منتشرة على النت، عموما أنا جبتها من هنا]

أصل المقالة دي كانت بوست كتبته على الفيسبوك، فحبيت أعيد نشرها هنا لتعم الفائدة...

أَبَا هِنْـدٍ فَلاَ تَعْجَـلْ عَلَيْنَـا *** وَأَنْظِـرْنَا نُخَبِّـرْكَ اليَقِيْنَــا
بِأَنَّا نُـوْرِدُ الـرَّايَاتِ بِيْضـاً *** وَنُصْـدِرُهُنَّ حُمْراً قَدْ رُوِيْنَـا
وَرِثْنَـا المَجْدَ قَدْ عَلِمَتْ مَعَـدٌّ *** نُطَـاعِنُ دُوْنَهُ حَـتَّى يَبِيْنَـا
-- عمرو بن كلثوم

اتكلمنا المقالة اللي فاتت عن تفكيك التعلق أو الاعتمادية في حالة الوراثة inheritance و قلنا الأفضل ما نروحش لسكة الوراثة - بقدر المستطاع - لكن لو لقينا إن الوراثة فعلا مفيدة عشان نعمل polymorphism على سبيل المثال، فعاوزين نقلل من آثارها الجانبية بقدر المستطاع، و ده موضوعنا في المقالة دي.

بالمناسبة...بما إننا اخترنا إننا نكمل في الوراثة، فالأبيات اللي في مقدمة المقالة بتتكلم عن الفخر، و هي من معلقات العرب اللي خدناها في إعدادي باين - على ما أذكر - 
ركزوا معايا بقى الله يكرمكم عشان الكلام اللي هانقوله هنا هو أرخم كلام في كل المقالات اللي فاتت و اللي جاية - إن شاء الله -

- أول مبدأ معانا في المقالة دي هو مبدأ البرمجة بالاتفاق Design By Contract
سنة 1986 في واحد برنس اسمه برتراند ماير Bertrand Meyer كان بيتكلم على طريقة في البرمجة سماها "البرمجة بالاتفاق" Design By Contract، المهم يعني كان بيقول عشان أجزاء البرنامج تتكلم مع بعضها لازم يحطوا اتفاق فيما بينهم، الاتفاق ده بيتكون من 3 بنود:
1- الشروط المسبقة preconditions: يعني مثلا عشان تنده method اسمها ConvertDocToPdf فالشروط المسبقة في الحالة دي ممكن تكون إن لازم يتبعت لها ملف doc سليم (يعني بيتبع التنسيق format القياسي standard المعتمد من مايكروسوفت) و الملف يكون موجود بالفعل مش null. تمام؟
2- الشروط اللاحقة postconditions: يعني مثلا في المثال السابق بتاع ConvertDocToPdf لازم نتيجة تنفيذ الmethod دي إن يكون عندي ملف Pdf سليم (يعني بالتنسيق format القياسي standard المعتمد من شركة أدوبي) يرجع من الmethod و يكون موجود بالفعل مش null. حلو؟
3- آخر حاجة بقى هي إن الثوابت invariants اللي في الclass ما ينفعش تتغير لكل الinstances بتاعت الclass اللي احنا شغالين عليها. مثال: لو عندنا class بتسهل لنا عمليات على التواريخ، و ليكن اسمها DateUtils. الحاجات اللي عمرها ما هاتتغير هاتكون مثلا إن الأيام هاتكون أي رقم ما بين 1 و 31، و الشهور هاتكون أي رقم ما بين 1 و 12،..إلخ. مثال تاني، لو عندي list: من الحاجات اللي عمرها ما هاتتغير فيها إن عدد العناصر فيها لازم يكون أكبر أو يساوي صفر...إلخ. الشاهد إن دي حاجة بتتحدد في كل class على حدة حسب هي بتعمل إيه، و مش شرط تكون موجودة في كل الclasses.

طيب هانعمل إيه بالمبدأ ده؟
ولا حاجة، ده بس مقدمة عشان نعرف نفهم المبدأ الرخم اللي جاي 
- المبدأ اللي بعد كده هو مبدأ لسكوف للاستبدال Liskov Substitution Principle (لسكوف دي واحدة ست بالمناسبة، واسمها بالكامل باربارا لسكوف Barbara Liskov)
ركز معايا بقى الله يكرمك عشان تفهم…
لو عندك class فيها virtual method و خليت class تانية تورث منها و تعمل override للvirtual method اللي في الbase class دي (يعني هاتعمل polymorphism) انتبه بقى لل3 حاجات اللي جايين دول:
1- الشروط المسبقة preconditions ما ينفعش تزيد في الderived class. خلونا ناخد نفس مثال ConvertDocToPdf اللي ذكرناه في المبدأ السابق، زي ما ضربنا أمثلة على الشروط المسبقة بإن الملف يكون doc سليم و مش null، لو جيت أعمل لها override في derived class ما ينفعش أقول إن حجم الملف ما يزيدش عن 3 ميجا مثلا، كده أنا زودت الشروط المسبقة. و ده هايزعل مدام لسكوف مننا. إنما لو غيرت طريقة التحويل، خليتها أسرع مثلا، أو حتى قبلت إن تنسيق الملف format ما يكونش قياسي standard فكده أنا قللت الشروط المسبقة وما خالفتش المبدأ.
2- الشروط اللاحقة postconditions ما ينفعش تقل في الderived class. خلونا ناخد نفس مثال ConvertDocToPdf اللي ذكرناه في المبدأ السابق، زي ما ضربنا أمثلة على الشروط اللاحقة بإن نتيجة تنفيذ الmethod إن يكون عندي ملف Pdf سليم (يعني بالformat القياسي المعتمد من شركة أدوبي) و مش null، لو حبيت أعمل لها override في derived class ما ينفعش أخلي الmethod تتساهل في الformat بتاع الملف الpdf اللي راجع بحيث يتكتب فيه حاجات مش قياسية مثلا، كده أنا قللت الشروط اللاحقة، و ده هايخلي مدام لسكوف تعيط. إنما لو غيرت في طريقة التنفيذ بحيث إني خليت التحويل أسرع أو قللت حجم الملف اللي راجع فكده أنا زودت الشروط اللاحقة وما خالفتش المبدأ.
3- الثوابت invariants اللي في الbase class - لو موجودة - ما ينفعش تتغير في الderived classes أبدا.خلونا ناخد نفس المثال بتاع DateUtils اللي ذكرناه في المبدأ اللي فات، الثوابت invariants اللي كانت عندنا إن مثلا الأيام لازم تكون رقم بين 1 و 31، لو عملت class بترث من الDateUtils دي لازم أحافظ على نفس الثوابت، ماينفعش بقى آجي ألاقي الأيام رقم بالسالب أو أكتر من 31، لأني بكده هاكون غيرت في الثوابت و أنت عارف الستات بتزعل قوي من الحاجات دي، ومدام لسكوف كده ممكن تنكد علينا.

لو وصلت مدام لسكوف للنقطة دي و جيت تسألها عن فايدة الكلام ده هاتقولك: لو كنت مهتم كنت عرفت لواحدك 
تعالوا نتكلم جد... بص يا سيدي، إنت كده تقدر تستخدم الobjects بتاعت الderived classes مكان الobjects بتاعت الbase classes و أنت مغمض عينك عشان هاتكون مطمئن إن سلوك البرنامج program behavior مش هايتغير. و عمرك ما هاتحتاج تعمل casting أو type checking عشان تعرف نوع الobject اللي أنت شغال عليه.
خد بالك إن مجرد إني أغير طريقة التنفيذ في الderived classes هذا لا يعد مخالفة لمبدأ لسكوف، طالما أنا ملتزم بال3 شروط بتوع البرمجة حسب الاتفاق. وفي الحالة دي أنا الحقيقة شايف إن المبدأ ده قريب جدا من مبدأ Open/Closed Principle الشهير، و ده هانتكلم عنه بالتفصيل في حلقة لاحقة - إن شاء الله -

عند النقطة دي بقى فيه هري و هري مضاد كتيير قوي هل دي تعتبر فايدة حقيقية و لا تنظير على الفاضي؟ وهل المبدأ ده أصلا يعتبر من مباديء الObject Oriented Design و لا لأ؟ والهري ده وراه شوية فلسفة على حبة تنظير رياضي زالفل.

المهم، إزاي أنا بستفيد من المبدأ ده - على المستوى الشخصي-؟
1- وأنا بكتب المقالة دي فكرت شوية لقيت إني بقالي كتير قوي بستخدم الوراثة inheritance في أضيق الحدود، و بعتمد بصورة كبيرة على الinterfaces لتحقيق الpolymorphism أو بطبق مبدأ تفضيل التركيب على الوراثة favor composition over inheritance - اللي اتكلمنا عنه في مقالة سابقة - وفي الحالات دي المبدأ ده مالوش لازمة.
2- روح المبدأ ده بتتكلم عن إنك لما تيجي تعمل وراثة تعملها صح، و الحقيقة فكرة إنك تسأل نفسك عن العلاقة بين الclasses هل يمكن اعتبارها is-a؟ بلاقيها أسهل كتير في تحديد القرار الصحيح. خلونا ناخد المثال العبيط اللي دايما بيستخدموه عشان يشرحوا مبدأ لسكوف، وهو مثال المربع و المستطيل: هل ينفع المربع يورث من المستطيل؟ بتطبيق مباديء البرمجة بالاتفاق - اللي اتكلمنا عليها فوق - هاتلاقي نفسك محتاج تراجع ال3 حاجات (الشروط السابقة، و الشروط اللاحقة، و الثوابت) عشان في الآخر تلاقيها ما تنفعش عشان هاتلاقي إنك محتاج في المربع تغير الطول مع العرض دايما، و ده بيخالف الشروط اللاحقة و/أو الثوابت للclass بتاعت المستطيل. سكة طويلة عشان تفكر فيها كده، خاصة لو المشكلة أعقد شوية من المثال العبيط اللي أنا ذكرته، صح؟ لكن لو سألت نفسك ببساطة: هل المربع يعتبر (=is-a) مستطيل؟ ولا هما الاتنين ممكن نعتبرهم (=is-a) شكل Shape، و يبقى هما الاتنين ليهم base class واحدة؟ أظن السؤال كده أسهل والإجابة واضحة.
3- آخر حاجة، الصراحة لو خالفت المبدأ ده مش بزعل ولا بحس بأي تأنيب ضمير عشان أنا غالبا مش بعمل inheritance إلا لما بكون مضطر ليها، و في الحالة دي بضحي بالمبدأ عشان البرنامج يعيش 
معلش طولت عليكم في المقالة دي عشان أصلا موضوعها رخم و كنت عاوز أشرحه بالتفصيل الممل، فلو فيه حاجة مش واضحة يا ريت تكتبوا في التعليقات.

شير في الخير بقى...سلام.

شوية مقالات بقى استفدت منهم
https://hackernoon.com/liskov-substitution-principle-a982551d584a
http://wiki.c2.com/?LiskovSubstitutionPrinciple

No comments:

Post a Comment