نقاط من كتاب Clean Architecture

بسم الله الرحمن الرحيم

هذه نقاط وفوائد من كتاب Clean Architecture لـ Robert C. Martin, أحدث هذه التدوينة بشكل أسبوعي تقريبا على حسب الإنجاز. الباب الأول, الحديث عن إثبات فاعلية وجدوى Software Design وأهميته.

  • بدأ الباب الأول بهذا السؤال الرائع, مالغاية من تصميم البرمجيات -Software Design-؟
    ثم أجاب: لتقليل عدد الموارد البشرية التي تبني وتحافظ على البرنامج.
    ونعني بتحافظ هنا, أنها تقوم بإضافة المميزات الجديدة -features-, ومعالجة الـ bugs السابقة.
  • هناك حقيقة في تطوير البرمجيات تقول:
    الطريقة الوحيدة للانطلاق بسرعة, هي الانطلاق بشكل جيد.

    The only way to go fast, is to go well

 

الباب الثاني عن القيمتين المهمتين, مميزات البرمجيات, ومعماريتها. 

  • لدينا قيمتين نريد تحقيقهم من خلال بناء التطبيق, الأولى طريقة عمل التطبيق (Behavior), والثانية المعمارية (Architecture)
    • المطور يوظف لغرض أن يقوم بجعل الأجهزة تعمل بطريقة تحفظ أو تحقق المال لصاحب التطبيق (stakeholder), بأن يقوم بعمل الوظائف المميزات التي يريدها صاحب التطبيق, وفي حال ظهرت بعض المشاكل -bugs- يقوم المطور باستخدام أدواته لصيانة التطبيق, لكن الإشكال أن المطور يظن أن هذه فقط هي مهمته.
    • ليحقق المطور كامل مهمته, يجب أن يقوم ببناء برمجيات سهلة التعديل, فلو قرر صاحب التطبيق إضافة ميزة أو إلغاء إخرى, يكون الأمر سهل وعملي.
  • جميل جدا, تساءل المؤلف تساؤلا عن القيمتين السابقتين, أيهما أهم, هل طريقة عمل التطبيق, أم معماريته؟ , ثم أعطى مثالين لحالتين يستطيع المتأمل معرفة الإجابة منها.
    • لو كان هناك برنامج يعمل بشكل رائع لكن يستحيل التعديل عليه, سيصبح عديم الفائدة
    • لو كان هناك برنامج لا يعمل, ولكن بني بشكل جيد, فستسطيع جعله يعمل, وتستمر في إضافة المميزات والعمل عليه.
  • مبدأ إيزنهاور هو أحد المبادئ المشهورة في التخطيط, والذي يقسم المهام إلى أربع أقسام,
    1- عاجل ومهم
    2- مهم وغير عاجل
    3- عاجل وغير مهم
    4- غير مهم وغير عاجل
    مميزات التطبيق هي دائما ذلك الشيء العاجل, ومعمارية التطبيق دائما هي الشيء المهم, نلاحظ أن معمارية التطبيق تقع ثانياً من ناحية الأهمية بينما المميزات تأتي في المرتبة الأولى والثالثة, الخطأ الذي يقع فيه مدراء المشاريع والمطورين أنهم يعطون دائما الأشياء العاجلة الأولولية على الأشياء المهمة, بمعنى أخر أنهم يخطئون في الفصل بين المميزات العاجلة والغير العاجلة فيقدمونها على الأشياء المهمة (المعمارية جيدة), الإشكال الذي يواجهه المطور هو أن مدراء المشاريع لا يعطون -يدركون أهمية- التوازن بين إطلاق مميزات جديدة والاهتمام بمعمارية النظام, لكن هذا يعتبر من صميم عمل المطور.
  • لذا ختم المؤلف هذا بالباب بموضوع عنونه بـ قاتل من أجل المعماريه (Fight for the Architecture), فريق التطوير الجيد, لابد أن يكون في يناضل من أجل الأشياء التي يؤمن أنها أفضل لمصلحة البرنامج, وتذكر كمطور تعد صاحب التطبيق -حتى وإن كنت فقط مطور-, لذلك اهتم ببناء تطبيق سهل التعديل والتطوير, أخيرا في هذا الباب حينما تأتي المعمارية في الأخير سيكون البرنامج مكلف في التطوير, وعمليا يعد التعديل مستحيلا, وحينما نصل لهذه المرحلة ندرك أن فريق التطوير لم يكن يناضل بشكل جيد لما يدرك أنه مهم.

أساليب البرمجة Programming paradigms
الباب الثالث عبارة عن نظرة عامة على الأساليب

هذا الباب كان تقديم عن الأساليب البرمجية التي ستناقش في الأبواب الثلاث القادمة وهي

  • Structure programming
  • Object Oriented programming
  • Functional programming

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

الباب الرابع  Structure programming

صاحب هذا الأسلوب البرمجي هو Edsger Wybe Dijkstra, الكتاب فصل فيه من ناحية الدافع لصاحب هذا الأسلوب والغاية من اتباعه هذا الأسلوب وطريقة اختباره, فقد كان Dijkstra يرى أن البرمجة شيء صعب, وأن المبرمجين لا يقومون بالبرمجة بشكل جيد, فاتبع هذا الأسلوب في تقسيم البرنامج لعدة أجزاء صغيرة.
ما هو الـ Structure programming ؟, نستطيع أن نقول أنه يتألف من:

1- control structure وهي أربعة أمور

  • Sequence, ونقصد فيها استدعاء الجمل والدوال بنفس الترتيب
  • Selection, ونقصد فيها جمله أو أكثر يتم تنفيذها بناء على حالة البرنامج, غالبا نستخدم معها if/else
  • Iteration, وهي جمل تتم تكرارها حتي يصل البرنامج لحالة محددة كـ for/ while
  • Recursion, وهي دالة تستمر في استدعاء نفسها حتى تصل لشرط التوقف

2- subroutines, هو المكون الثاني لـ Structure programming ونعني به الدوال (functions)

3- blocks, هو أسلوب معرف لجمع مجموعة من الجمل في مكان واحد حيث تنفذ كأنها جملة واحدة.

يسمى هذا الأسلوب البرمجي كثيرا بـ modular programming لأنه كما ذكرنا يعمد إلى تقسيم الكود لأجزاء صغيرة, حاليا جميعنا نكتب بهذا الأسلوب, ليس عن طريق الأختيار بل لأن اللغات التي نكتب بها تجبرنا على هذا الأسلوب.

كما يشدد في هذا الأسلوب البرمجي على عدم استخدام جملة goto, ونشر فيها Dijkstra مقالة بعنوان go to statement considered harmful, والتي لم تعد موجودة في عدد من لغات البرمجة الحديثة.

الباب الخامس  Object oriented programming

بدأ الكاتب هذا الباب بالتعريفات المشهوره لهذا الـ paradim -الأسلوب-: هو عبارة عن بيانات ودوال, وبين أنه تعريف ليس بالجيد, وهذا واضح جدا فجميع الأساليب البرمجية ماهي إلا عبارة عن بيانات ودوال, ثم ذكر تعريف أخر وهو أن هذا الأسلوب هو تمثيل لمشكلة حقيقة, ثم بين بعدها أن هذا بلا شك ليس تعريفا يعطي المعنى الحقيقي لهذا الأسلوب -عبّر عنه بتعريف مخادع-, لا شك أننا نستطيع القول أن OOP يمثل الحل بطريقة تشبه الواقع لكن لا يسمى ذلك تعريفا, لكن نستطيع التعبير عن طبيعة هذا الأسلوب بثلاث كلمات سحرية -على حد قوله- وهي, encapsulation -التغليف- و Inheritance -الوراثة- و polymorphism -التعددية-
لم يفصل الكاتب في هذا الباب في مفاهيم الـ OOP بل نوه إلى أهم المفاهيم وهي التي ذكرنا سابقا, ثم استطرد في ضرب أمثلة بـ sturcture programming بلغة C وكيف يمكن عن طريقها تحقيق المفاهيم الأساسية في OOP, ليبين أنه فعليا هذه المفاهيم موجودة ومطبقة بغير الـ OOP (رغم أني أرى أن بعض الأمثلة التي طبقها ليست بنفس المرونة الموجودة في OO ولا يمكن تطبيقها على تطبيقات أكبر من التي مثل عليها), وبعد ذلك تكلم الـ dependency inversion وشرع بتوضيحه بطريقة رسومية واستخدم UML digram, ليوصل في نهاية هذا الباب أن الإضافة الحقيقة لهذا الأسلوب البرمجي هي أنه يمكّن المبرمج من تطوير تطبيقات بطريقة ما سماه plugin architecture وهي أن يقسم التطبيق لوحدات -modules- تحوي قواعد عامة لتطبيق, وأخرى تحوي تفاصيل التطبيق الدقيقة.

OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system. It allows the architect to create a plugin architecture, in which modules that contain high-level policies are independent of modules that contain low-level details

الباب السادس  Functional programming

هذا الأسلوب البرمجي يعتمد بشكل كبير على مفهوم λ-calculus ما يعرف بـ lambda المخترع عن طريق العالم الرياضي الأمريكي Alonzo Church في عام 1930م.

بدأ الكاتب هذا الباب بإعطاء نظرة سريعة عن ماهية هذا الأسلوب فقام بوضع هذا المثال

كود بسيط بلغة جافا يطبع مربع ٢٥ رقم, ثم مثل مثالا أخر بلغة Clojure يقوم بنفس العملية

ليفصل بعد ذلك في كيفية عمل هذا الكود وماذا يعني كل قوس وكلمة فيه, لينهي هذا التفيصل المبسط بأنه ليس الغرض من هذا الباب شرح Clojure أو الـ Functional programming, لكن كان الغرض من هذا المثال الأشارة للفرق الجوهري بين جافا و Clojure أو بين OOP و FP وهو mutable variable -المتغير الذي يغير حالته -state- أثناء عمل البرنامج-, وهو المتغير i داخل loop حيث نرى أنه تتغير قيمته فيزيد في كل دورة من 0 إلى 25 بينما المتغير x في Clojure لم ولن يتغير طوال عمل البرنامج, هذا يقودنا إلى جملة مهمة وهي: المتغيرات في Functional programming لا تتغير.

جميل جدا, مالذي يجعل موضوع immutability مهما؟, فعليا كثير من الإشكالات تحدث من المتغيرات القابلة لتغيير, أو بعبارة أبسط معظم المشاكل التي نواجهها في concurrent applications -التطبيقات التي تتطلب أكثر من thread- لا يمكن أن تحدث إذا كان لم هناك mutable variable.

فالتطبيقات ذات المعمارية الجيدة تفصل التطبيق لعدة أجزاء منها ما يمنع تغير الحالة -Immutable- وأخرى تسمح بذلك-mutable-.

 

بعد الحديث عن الثلاث أساليب في كتابة الكود نصل إلى ثلاث تعريفات واجهت مشكلة في ترجمتها ففضلت نقلها كما هي:

• Structured programming is discipline imposed upon direct transfer of control.
• Object-oriented programming is discipline imposed upon indirect transfer of control.
• Functional programming is discipline imposed upon variable assignment.

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

فيقول الكاتب في نهاية الحديث عن أساليب كتابة الكود, إن البرمجيات تتألف من sequence, selection, iteration, و indirection, لا أقل من ذلك ولا أكثر.

هنا ندخل في مفاهيم الـ SOLID 

كما هو معلوم كتابة برمجيات جيدة تبدأ بكتابة شفرة جيدة, وهنا تأتي مفاهيم الـ SOLID لتساعدنا في تنظيم الدوال والـ classes والية الترابيط بينها, وتهدف في نهاية المطاف لتحيقيق ثلاثة أمور

  1. قابلية التعديل
  2. سهولة في فهم الكود
  3. بناء component قابلة للاستخدام في أكثر من مشروع

نأخذ نظرة سريعة عن ماذا تعني كلمة SOLID

 

  • S = Single Responsibility principle
    – يجب أن يكون هناك سبب واحد لتغيير وحده من النظام.
  • = Open/Closed Principle
    – ليكون النظام سهل التعديل يجب أن يكون قابل للإضافة بدلا من التعديل الشفرة الموجودة.
  • L = Liskov substitution principle
    – لتبني نظام من أجزاء قابلة لتغيير, يجب أن تلزم هذه الأجزاء بعقد-أسلوب بناء- يسمح بإستبدالها بغيرها.
  • = Interface Separation
    – يحض على عدم الاعتماد على أشياء لا تستخدمها.
  • D = Dependency Injection
    – الشفرة التي تضع القوانين العامة يجب ألا تعتمد على الشفرة التي تنفذ التفاصيل.

الباب السابع SRP: The Single Responsibility Principle

حينما يذكر هذا المبدأ يظن الشخص أنه يجب أن يكون الـ module مسؤول عن شيء واحد فقط, وإن كان ذلك قد يكون فيها جانب من الصحة, لكن هذا المبدأ ينطلق من منطلق أن بناء البرمجيات يتأثر بالمستفيدين منه, فلذلك يجب أن يكون كل جزئية في البرنامج يمكن أن يؤثر عليها شخص واحد, فمثلا, لو كان المشروع تطبيقا لإدارة الموظفين وكان Emplyee class كالتالي

لو فرضنا أن دالتي reportHours و calculatePay يعتمدون في استخراج النتائج على دالة أخرى تسمى regularHours()  وقرر CFO أن يحسب ساعات الدوام الإضافي بضعف ساعات الدوام العادي, وقام بتعديل القيم داخل الدالة, وطبعا بحكم أن COO ليس له علاقة بالموضوع فمن الطبيعي إلا يعلم عن التعديل, لكن سيستمر في تلقي تقارير خاطئة من النظام, و السبب تداخل أكثر من طرف -actor- في نفس النقطة.
ولو فرضنا أن CFO طلب تعديل و COO طلب تعديل وتم تقسيم المهمة بين عضوين من فريق التطوير وقبل إطلاق النظام تفاجئوا بوجود تعارض -conflict- في الشفرتين أي أن إحداهمها قد تلغي شيء من الأخرى, فقاموا بعمل دمج -merge- مع فصل الأمور المتداخلة, بمجرد قراءتك لهذه النقطة قد تشعر بالارتباك, بالفعل ستصبح الشفرة معرضه للخطأ بشكل كبير, لذا نصل لهذه الخلاصة, يجب أن يكون كل module مسؤول عن طرف واحد, وواحد فقط.

A module should be responsible to one, and only one, actor.

فلهذه المشكلة عدة حلول منها هذا الحل المباشر وهو أن نقوم بفصل كل عملية في class منفصل كالتالي

ونقوم بعمل نفس الشيء لبقية الأقسام, وإذا رأيت أن الموضوع كبر عليك, قد يكون facade pattern حل مناسب لتسهيل التعامل مع هذه الـ classes.

الباب الثامن OCP: The Open Closed Principle

البرمجيات الجيدة هي التي تكون قابلة للإضافة ومغقلة لتغيير

A software artifact should be open for extension but closed for modification.

بصيغة أخرى classes الموجودة يمكنك الوراثة منها وإضافة مميزات جديدة دون الحاجة لتعديل عليها.
يعتبر هذا المبدأ مهم جدا في كتابة الأنظمة, وهو ببساطة يقسم النظام إلى أجزاء هرمية, الأجزاء العلوية منها تكون قواعد عامة لنظام -abstract- والجزاء السفلية هي الإضافات. لنأخذ مثال شائع الأستخدام لشرح هذا المفهوم

لدينا رجل الي يعمل في المباني فكنا نقوم بحساب مساحة مناطق العمل عن طريق إنشاء class Rectangle

ومن ثم نرسلها لـ AreaManager

واجهنا نوع من القاعات الرسمية يكون فيها شكل منطقة العمل حلقي أو ما نسميه دائري فأنشأنا نوع أخر

وقمنا بالتعديل على AreaManager

ولو واجهنا مناطق مثل حلبات octagon سنظيف ونعدل في أكثر من class, وهذا لا يحقق ocp الحل يكون كالتالي

وكل شكل من الأشكال يقوم بعمل extension لهذا الـ interface بهذه الطريقة

ونستطيع أن نظيف classes أخرى دون أن نضطر لإضافة سطر واحد في class AreaManager الذي سيصبح شكله كالتالي

 

الباب التاسع LSP: The Liskov Substitution Principle

يقول الكاتب نقلا عن Barbara Liskov صاحب هذا المبدأ

What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

قد يكون التعريف السابق غاية في التعقيد, دعنا نبسط الأمر ونقول يجب أن يكون Object قابل لأن يستبدل بـ Object أخر من subtype الخاص به, دون أن يكون هناك خطأ في البرنامج.

يعتبر أحد أهم المفاهيم التي ينبغي تطبيقها لتحقيق معمارية جيدة قليلة أو خالية من الإشكالات – polluted  كما سماها الكاتب-

الباب العاشر ISP: THE INTERFACE SEGREGATION PRINCIPLE

لنفرض أن لدينا interface لديه ثلاث a() b() c() methods ولدينا ثلاث classes مستخدمين هذا الـ interface وكل class يحتاج فقط دالة واحدة من هذه الثلاث دوال, في أي تعديل تقوم فيه على هذا الـ interface سيتأثر فيه الثلاث classes حتى لو كانوا لا يستفيدون من هذا التعديل أو أن الدالة التي تم تغييرها لا تعنيهم, هنا تأتي اهمية الـ ISP حيث يفترض أن يتم فصل الدوال في interfaces مختلفة, فيكون في كل interface دوال متعلقة ببعضها, ويفترض أن الـ class الذي يعمل Implementation لهذا الـ interface يحصل على الدوال التي يحتاجها فقط..

بعبارة أخرى مختصرة, لا يجب أن يعتمد أي class على دالة لا يستخدمها.

اترك تعليقاً