ماذا نعني بال Coupling وال Cohesion
كثيراً ما نسمع عن هذا المصطلحان وأنه يجب أن تسعى الى أن يكون الكود قليل التداخل Low Coupling وأكثر تجانساً High Cohesion بدون توضيح مناسب لمعناها، في هذه المقالة سوف نوضح هذه المصطلحات والفرق بينهم بشكل مبسط ونوضح طرق مختلفة لتحقيق ال loose coupling عن طريق طرق التصميم Design Patterns المختلفة.
نظرة تاريخية
كما في كثير من الأفكار في علوم الحاسب فهي لم تتولد فجأة بين ليلة وضحاها في رأس أحدهم، وانما هي فكرة طرحها باحث وبعدها بعدة سنوات يناقشها او يطورها باحث اخر وهكذا فإن بدايه هذه الأفكار منذ السبعينيات.
فترة من 1970 الى 1980 سميت بفترة “أزمة البرمجيات Software Crisis ” حيث واجهوا تعقيداً وصعوبة في تطوير البرمجيات وظهرت مشاكل التأخير في التسليم، زيادة الميزانية وقلة الجودة، ولهذا عجت بالكثير من الأبحاث في هندسة ومعمارية البرامج، وبها ظهر الكثير من المفاهيم في تصميم البرمجيات، ومنها –موضوع حديثنا-مفهوم ال Coupling وال Cohesion.
بدء الأمر في عام 1972 حيث كتب الباحث ديفيد بارناس Davied L. Parnas ورقة علمية بعنوان On the Criteria To Be Used in Decomposing Systems into Modules وقارن فيها بين طريقتين لتقسيم Decomposing الأنظمة الى وحدات صغيرة Modules، وتوصل في خلاصتها أنه يجب تقسيم النظام الى وحدات على حسب مدى قابليتها لكي تتغير، بمعنى الأشياء التي يتوقع أن تتغير لسبب معين تفصل عن الوحدات الأخرى التي قد تتغير لسبب مختلف عنها.
الباحث David Lorge Parnas من أوائل من بحثوا في هندسة البرمجيات واسهم في تعريف مفهوم ال Information Hiding في ال Modular Programming
بعد ذلك بسنتين تحديداً، كتب العالم ديسكترا Edsger Dijkstra ورقة بعنوان On the role of scientific thought وفيها عرف مفهوم ال Separation of Concerns ويطلق عليه اختصاراً SoC، وفكرته كانت عبارة عن تقسيم النظام الى عدة خصائص أو طبقات Layers ليست متداخلة Non-Overlapping Features، كل خاصية Feature تريدها في النظام سوف تمثل Concern فيه وهي تسمح للكود بأن يكون أكثر Modularized وقابل لإعادة الاستخدام.
هذه الفكرة طبقت لاحقاً في الكثير من اللغات والأدوات، مثلاً من أشهر الأمثلة الحالية كتابة ال HTML لوصف المحتوى وبينما ال CSS لإظهار وتنسيق المحتوى وبالتالي فصلنا الاهتمامات، ايضاً نمط ال MVC هو أحد الأمثلة الأخرى على فكرة ال SoC. وال SoC وهي تعتبر من الأمور المهمة في تصميم الأنظمة وخصوصاً ال Enterprise Systems.
بعد عدة سنوات ألف لاري قسطنطين ومجموعه من الباحثين كتاباً باسم Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design وتحدثوا فيه عن البرمجة الهيكلة وعرفوا فيها مفهوم ال Coupling وال Cohesion والذي سنوضحه فيما يلي.
أواخر التسعينات قام روبرت مارتن (المعروف بأنكل بوب) بتجميع هذه التعاريف على شكل مفهوم وسماه مفهوم ال Single Responsibility Principle بالإضافة الى عدة مفاهيم اخرى سماها بأساسيات تصميم البرمجيات.
قام بعدها Michael Feathers بتسمية هذه المفاهيم بالاسم SOLID (كل ما تحتاج معرفته عن مفهوم ال SOLID) بعد ترتيبها واخذ الحرف الأول من كل منها (ومايكل هو مؤلف لأحد الكتب المرموقة في البرمجيات وهو كتاب Working Effectively with Legacy Code ).
ال Coupling وال Cohesion مرتبطان ببعضهما البعض، والنظام المصمم بحيث يحقق الLow Coupling وال High Cohesion بشكل عام يكون قد حقق المقروئية العالية، قابلية الصيانة، سهولة الاختبار، واعادة الاستخدام. وفيما يلي نوضح الفكرة الأساسية لكل مصطلح مع مثال مناسب له.
ملاحظة هذه الأفكار يمكن تطبيقها سواء في البرمجة الهيكلية او حتى الكائنية وليست حصراً على ال Object Oriented فهي قدمت اساساً قبل ظهور اللغات الكائنية لدعم ال Structured Design، والتصميم الكائني الجيد يتميز بال Low Coupling وال High Cohesion والذي يعني كل الكائنات (High Cohesion) تتعامل مع الكائنات الاخرى من خلال Interface مستقرة (Low Coupling).
التجانس Cohesion
وهي تشير الى التشابة او الترابط داخل كود ما (سواء كانت دوال داخل كلاس، أو كلاسات داخل namespace ما)، فكلما كانت تلك الأسطر متعلقة ومترابطة فيما بينها فهذا يعني أن تلك الوحدة لها High Cohesion، وكلما كانت تلك الدوال او الاسطر غير متعلقة ببضعها كلما كانت تلك الوحدة Low Cohesion.
إذا نظرت لتعريف ال Cohesion في مجال اخر مثلاً الكيمياء ستجد تعريف ايضاً مناسب مشابه لل Cohesion في البرمجيات، وفي الكيمياء التماسك هو خاصية للمادة تشير الى الجاذبية الموجودة بين الجزئيات الشبيهة داخل الجسم.
في المثال التالي سوف نجد كلاس لإرسال البريد وسوف تجد أن كل شيء فيه متعلق لإنجاز نفس الهدف، وهذا يعني أن الكلاس له High Cohesion.
بينما في هذا المثال سوف تجد أن هناك دالة تقوم بتوليد تقرير واستخراج النتائج ومن ثم عرضه بصيغة HTML وهي فعلياً لا علاقة لها بهدف ذلك الكلاس، ولذلك هذا الكلاس له Low Cohesion.
الكلاسات ال Highly Cohesive هي أكثر قابلية للصيانة واعادة للاستخدام بسبب أنها لا تحتوي على أي اعتمادية Dependencies، بعكس الكلاسات ال Low Cohesive تصعب فهم الكلاسات وتقدم لك هشاشة في البرنامج، أيضاً تقليل ال Cohesion يؤدي الى انشاء كلاسات تحتوي على مسؤوليات (دوال) كل منها له وظيفة بعيدة عن الاخرى.
لذلك دائماً ما ينصح الى انشاء كلاسات متخصصة بدوال قليلة تكون متقاربة في الوظيفة منطقياً، وعندنا تجد دوال متباعدة في الوظيفة في ذلك فقد تكون بحاجة لنقلها الى مكان أخر.
وارد كانينغهام Ward Cunningham (من رواد ال Extreme Programming من أول من برمجوا أنظمة الويكي) يقول :
بأن درجة التجانس يتناسب عكسياً مع عدد الدوال في الكلاس
وهذا تعريف مبسط وفي نفس يوضح الفكرة من التماسك بشكل جيد.
الترابط Coupling
وهي تقيس درجة الاعتمادية بين أكثر من وحدة في البرنامج ، مثلاً بين الكلاسات لو كان لدينا كلاس A وكلاس B فنقول أنهما مترابطان Coupled عندما تحتاج لعمل تغيير في الكلاس B كل مرة تقوم فيها بتغيير كلاس A. بعبارة اخرى انت تريد تغيير شيء في كلاس A ولكن بسبب الاعتمادية فسوف تجبر على أن تقوم بتغيير الكلاس B والا فالكود لن يترجم ولن يعمل.
الترابط Coupling يقاس ايضاً من قليلة الى عالية ، وكلما كانت أقل كلما كان أفضل.
وال Low Coupling لا تعني أن ال Modules معزولة تماماً عن بعض، فهي بالتأكيد سوف تحتاج للتواصل Communicate فيما بينها ولكن عن طريق Well Defined Interfaces. وكل Module سوف يعمل بدون الحاجة لمعرفة تفاصيل عمل ال Module الأخر. وبالعكس ال High Coupling سوف يعيق من اختبار الكود واعادة استخدامه ايضاً وجعل الكود أكثر صعوبة في الفهم وهو أحد الأسباب في التصاميم الجامدة Rigid والهشة Fragile.
مثال: في الكلاس التالي نجد أنه متعلق بشكل قوي في ال MySQLDatabase (سواء استقبله كمعامل في دالة البناء او انشئه داخله) ونقول عنه high coupling
بينما التعديل التالي قمنا بإضافة contract بينهم وبالتالي ال StorageManager لا يعلم شيئاً عن نوع ال Implementation سواء كان على MySQL أو على ملف عادي أو أي نوع، وهذا ما نسميه بال Low Coupling.
للمزيد حول ال interface وكيف يساهم في جعل الكود أفضل، وال Repository Pattern فانظر للكتاب العربي: البرمجة باستخدام ال interface.
لذلك من أهم الأمور في الكود البرمجي هو أن يكتب بشكل Loose Coupling ويقلل التداخل بين الوحدات فيه، حيث هي تقلل الصيانة وتجعل الكود أسهل في التطوير والتعديل في المستقبل.
قد تتساءل الآن وتقول كيف يمكن لل low coupling أن تقدم هذه الفوائد، وهذا ما سيتبين اليك في الفقرة التالية وكيف يمكن لبعض ال design patterns أن تساهم في ذلك.
لماذا ال Tight Coupling تقلل من قابلية الصيانة؟
لنأخذ مثال في الحياة الواقعية وقد تجد في بعض الفنادق منخفضة التكلفة مجففات شعر موصلة مباشرة بالحائط كما بالصورة، ربما لأنهم لا يثقوا بالزبائن وبالتالي قاموا بإيصالها الى الحائط مباشرة بهذه الطريقة الرديئة، فماذا يحدث إذا تعطل الأول وأرادوا تركيب مجفف جديد؟ بالطبع باستخدام أدوات خاصة لذلك يتم فك القديم وتركيب الجديد وربما يحتاج الى إطفاء الكهرباء من الغرفة في تلك الفترة.
هذا مشابه للكود المرتبط Tightly Coupled Code فهو مثل المجفف المرتبط بالحائط ولا يمكن تغييره بسهولة بدون التأثير في أكواد أخرى.
في العادة فلا يتم توصيل الاسلاك على الحائط كما في الصورة السابقة، ويتم استخدام القابس Plugs والمقبس/المأخذ Socket، فالمقبس socket يمثل المكان الذي يتم تركيب القابس plug عليه ويجب أن يتطابقا وبالتالي يمكن تركيب اي جهاز على المقبس في حال كان له نفس الشكل. وفي البرمجيات فالمقبس socket فهي ال interface.
باستخدام ال Sockets and plugs فمجفف الشعر الان أصبح loosely coupled
فكرة المقابس والأقباس سبقت الحواسيب بعدة عقود وهي التي تقدم الخدمة الاساسية للحواسيب ايضاً، وبالرغم من ان المصممين للمقابس sockets لم يعرفوا الحواسيب أو الأجهزة المستقبلية ولكن تصميمها مرن يجعل اي جهاز حتى لو لم يعرف ذلك الجهاز.
الذي يهم هنا أنه يمكن ان نقارن بين ذلك النموذح مع العديد من ال software design and principles:
أولاً: لن تكون محكوماً فقط بالمجفف، وتستطيع توصيل اي جهاز اخر مثلاً Computer، وبكل بساطة تنزع unplug المجفف وتوصل Plug الحاسب في نفس ال socket.
القدرة على تبديل طرف واحد بدون التأثير على الاخر هي مشابه لفكرة اساسية في تصميم البرمجيات ألا وهي ال Liskov Substitution Principle وينص هذا المبدأ على أنه يجب أن تكون لديك القدرة على تبديل أحد ال Implementation لل Interface بأي Implementation أخر بدون أي تأثير Breaking على الكلاينت، وهو يعتبر من أهم مبادئ تصميم البرمجيات حيث يمكننا اضافة المتطلبات في المستقبل حتى لو لم نكن على علم بماهيتها.
الصورة التالية، تبين أنه يمكن يمكن فك توصيل unplug الحاسب في حال لم نعد نحتاج لتوصيله، وحتى في حال لم يمكن هناك شي موصل بالمقبس فان المقبس لن ينفجر.
في البرمجيات ففي الغالب فال client يتوقع الخدمة أن تعمل Available بشكل دائم، فاذا تم حذفها لأي سبب فسوف يحصل على الكلاينت على خطأ مثلاً NullReferenceException، وللتعامل مع هذه الحالة فيمكن عمل Implementation من interface لا يقوم بفعل شيء does nothing. وهذه من طرق التصميم design pattern وتعرف بالاسم Null Object، وهي وضعية عدم توصيل اي جهاز بالoutlet.
فطالما نحن نطبق ال loose coupling فيمكن تبديل اي Real Implementation بأي Implementation أخر سواء يقوم بمهمة أو لا بدون التسبب في اي مشاكل.
ثانياً: هناك العديد من الأمور الاخرى يمكن فعلها، مثلاً إذا كنت تعيش في منطقة متقطعة الكهرباء فيمكن أن توصل جهاز Uninterrupted Power Supply (UPS) بالمقبس وتوصل الحاسب بال UPS.
كل من الحاسب Computer وال UPS لديه مهمته الخاصة Single Responsibility ولا يتعارضان مع بعضهما، بالرغم من أن كل منهم قد صنع بواسطة مصنع manufactures مختلف، وربما في اوقات مختلفة وفي دول مختلفة. وحتى انه يمكن تشغيل الحاسب بدونه، ويمكن تشغيل ال UPS ايضاً مع اي جهاز أخر مثلاً مجفف الشعر.
في البرمجيات فان اضافة الخصائص لشي موجود يشابه مبدأ Decorator design pattern حيث تستطيع اضافة المزيد من الخصائص أو حتى اضافة ال CROSS-Cutting Concerns بدون الحاجة لإعادة كتابة أو تغيير كمية كبيرة من الكود لديك.
طريقة اخرى لإضافة خصائص اخرى لكود موجود هي عن طريق اضافة new implementation تحت كائن واحد وبالتالي تستطيع اضافة او حذف اي عدد تريده من ال Implementations وسيتم التعامل مع كائن واحد يمثل الواجهة لهم، وهذا ما يعرف بال Composite Pattern، في المقبس الكهربائي إذا كنت تريد ان تشغل أكثر من جهاز في نفس المكان فيمكن ان تستخدم توصيلة كهربائية بها أكثر من منفذ وقابسها سوف يركب على المقبس مباشرة، الصورة التالية تبين ذلك.
التوصيلة لديها مقبس واحد plug يتم توصيلة على ال socket، ولكن في نفس الوقت يزودك بالعديد من ال sockets للأجهزة الأخرى، وهذا يمكنك من اضافة وازالة المجفف بينما الحاسب يعمل. وبنفس الفكرة فال Composite pattern يسهل لك اضافة وحذف الخصائص عن طريق تعديل مجموعه من ال composed interface implementations.
ثالثاً: احياناً نقع في مشكلة ان القابس plug لا يدخل في المقبس socket وتجد ان المكان غير متطابق حتى يدخل، لذلك تستخدم محول adapter يسهل لك تركيب الجهاز، وبنفس الأمر في البرمجيات فال Adapter design pattern تقوم بنفس المهمة فهي تستطيع وضع واجهة جديدة للكود الذي تريده حتى يعمل ويتوافق مع الكود الأخر.
وهذا مفيد عندما يكون لديك API من مكتبة خارجية Third-Party API تريد أن تشغلها في كود لديك يتعامل مع interface محددة فتقوم بوضع تلك المكتبة وتحول ال API لها الى الشكل المناسب مع ذلك الكود باستخدام ال Adapter Pattern.
الشيء المثير للدهشة في مثال ال Socket وتوصيلاتها، فبالرغم من أنها موجودة من عقود مضت فهي سهلة وتستخدم بنفس الطريقة بمجرد وضع الاساس فيمكن ان توصل أي جهاز بسهولة سواء مباشرة أو باستخدام adapter أو اي مطلبات اخرى قادمة.
لذلك نفس الأمر في البرمجيات فال loose coupling يجعل الكود اسهل كثيراً في ال Maintainable.
في مقالات قادمة ان شاء الله سوف نتحدث عن ال SOLID design principles وطرق التصميم design patterns المختلفة التي تحدثنا عنها بأمثلة برمجية.
نراكم على خير.
المراجع:
- https://www.amazon.com/Dependency-Injection-NET-Mark-Seemann/dp/1935182501
- https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html
- http://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/
- http://enterprisecraftsmanship.com/2015/09/02/cohesion-coupling-difference/
- https://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/
- http://codurance.com/2016/03/03/cohesion-cornerstone-software-design/
- https://dotnetinterviewquestion.wordpress.com/2014/05/15/explain-cohesion-and-coupling-net-and-c-architecture-level-interview-question/
مقال ممتاز جدا و مطروح بطريقة علمية وسلسلة ويتضح بها المجهود والتنقيح والاخراج المعمول بعناية.
بارك الله فيك وفيما ما تطرح من معلومات قيمة.
في الحقيقة في ظل غياب الـ Cohesion فانه مقروئية الكود وخصوصا المكتبات تكون بلا شك صعبة جدا لعدم وضوح الهدف الصريح من كلاس ما.
و في نظري, على أية حال, صحيح أن الــ Coupling يعتبر غالبا عيبا جارحا حيث يكثر فيه اعتمادية الأجزاء ببعضها البعض ولكنها على الأقل تجعل مقروئية الكود أكثر وضوحها ويتضح مدى ارتباط جزء ما بآخر. ولكن بالتالي فإنه وبنظرة مستقبلية يجعل من الإصلاح والتعديل واستبدال الأجزاء والـ agility بشكل عام أصعب.
باش مهندس وجدي جزاك الله كل خير
شرح موفق وعملي.
للاسف هذه الموضوعات نادرا ما تناقش رغم اهميتها لمن يريد الاحتراف في مجال هندسة البرمجيات .
اتمنى ان تتابع خصوصا مع الباترن ديزاين
باذن الله واذا كنت تريد المشاركة معتا في الكتابة والاعداد فتواصل معنا عبر صفحتنا في الفيس بوك أو التويتر، وشكراً لكم.
جزاكم الله خيراً على هذا المقالزز
المقال*