أذكرها وكأنها البارحة، كانت ليلة خميس هادئة، وفنجان القهوة بالهيل بجانبي، والفريق يستعد لإنهاء الأسبوع بسلام. وصلنا طلب “بسيط وعاجل” من العميل: “نريد تغيير تنسيق التاريخ في تقرير المستخدمين من `DD-MM-YYYY` إلى `YYYY/MM/DD`”. طلب أبسط من البساطة، أليس كذلك؟
تطوع أحد المبرمجين الشباب لإنجاز المهمة، وقال بثقة: “ربع ساعة وبتكون عندك يا أبو عمر”. مرت نصف ساعة، ثم ساعة، وبدأ القلق يتسلل إلى قلبي. ذهبت لأرى ما به، فوجدته غارقاً في العرق والارتباك. قال لي بصوت مرتجف: “يا أبو عمر، غيّرت تنسيق التاريخ، بس نظام تسجيل الدخول كله انضرب! المستخدمون ما عادوا قادرين يفوتوا على حساباتهم!”.
صعقتُ للحظة. شو دخل تنسيق التاريخ في تقرير بتسجيل الدخول؟! كأنك غيّرت لمبة في الصالون فانقطعت المياه عن المطبخ! بعد ليلة طويلة من التنقيب والتحقيق في الشيفرة البرمجية، اكتشفنا الوحش: كلاس ضخم اسمه `UserService` كان مسؤولاً عن كل إشي بيتعلق بالمستخدم. تسجيل مستخدم جديد، تسجيل الدخول، استرجاع كلمة المرور، تعديل الملف الشخصي، إنشاء تقارير، تصدير التقارير… كل شيء في مكان واحد. وعندما عدّل زميلنا الشاب على جزئية صغيرة تتعلق بالتقارير، أثر هذا التغيير بشكل غير متوقع على دالة أخرى تستخدم نفس المتغيرات أو الإعدادات في عملية تسجيل الدخول. كانت ليلة من الجحيم، لكنها كانت أيضاً الدرس الأهم الذي تعلمه الفريق بأكمله.
ما هو جحيم الشيفرة المتشابكة (Spaghetti Code)؟
ما مررنا به في تلك الليلة هو مثال كلاسيكي لما نسميه في عالم البرمجة بـ “الشيفرة المتشابكة” أو “كود السباغيتي”. تخيل صحن سباغيتي، هل يمكنك سحب خيط واحد دون أن تسحب معه مجموعة أخرى؟ مستحيل. هذا هو بالضبط حال الكود المتشابك.
هذا النوع من الكود له عدة أعراض كارثية:
- صعوبة الفهم: تحتاج لوقت طويل جداً لتفهم ماذا يفعل جزء معين من الكود، لأنه يعتمد على أجزاء أخرى كثيرة.
- صعوبة التعديل: كما رأيتم في قصتنا، أي تعديل بسيط قد يؤدي إلى سلسلة من الأخطاء غير المتوقعة في أماكن لا تخطر على البال. هذا ما يسمى بـ “الآثار الجانبية” (Side Effects).
- صعوبة الاختبار: كيف ستكتب اختباراً لوحدة برمجية (Unit Test) وهي مرتبطة بكل شيء آخر في النظام؟ الأمر شبه مستحيل.
- إعادة الاستخدام؟ انسَ الأمر: من الصعب جداً أخذ جزء من هذا الكود واستخدامه في مكان آخر، لأنه “ملزّق” في سياقه الأصلي.
نصيحة أبو عمر: إذا شعرت بالخوف من تعديل جزء من الكود لأنك لا تعرف ماذا قد يحدث، فهذه علامة حمراء كبيرة على أنك تتعامل مع شيفرة متشابكة.
المنقذ: مبدأ المسؤولية الواحدة (Single Responsibility Principle – SRP)
بعد تلك الليلة، عقدت اجتماعاً مع الفريق، وكان الموضوع الوحيد على الطاولة هو مبادئ SOLID، وبشكل خاص الحرف الأول ‘S’ الذي يرمز إلى “مبدأ المسؤولية الواحدة” أو Single Responsibility Principle.
التعريف البسيط (لكنه عميق)
ينص المبدأ على أن: “الكلاس (أو الوحدة البرمجية) يجب أن يكون له سبب واحد فقط للتغيير”.
لحظة! شو يعني “سبب واحد للتغيير”؟
المقصود هنا ليس “تغيير الكود” بحد ذاته، بل “سبب عملي أو منطقي” (Business Reason) يدفعك لتغيير الكود. لنعد إلى مثالنا: كلاس `UserService` كان له أسباب متعددة للتغيير:
- إذا تغيرت قواعد التحقق من هوية المستخدم (Authentication logic).
- إذا تغيرت طريقة تخزين بيانات المستخدم في قاعدة البيانات (Persistence logic).
- إذا تغير شكل أو محتوى التقرير (Reporting logic).
- إذا تغيرت طريقة تصدير التقرير (مثلاً من PDF إلى Excel).
أربعة أسباب مختلفة تماماً! هذا يعني أن أربعة أقسام مختلفة في الشركة (الأمان، قواعد البيانات، تحليل الأعمال، إلخ) قد يطلبون تغييرات على نفس الكلاس، مما يخلق فوضى عارمة.
تطبيق المبدأ عملياً: تفكيك الكلاس الوحش
الحل كان واضحاً: يجب أن نقوم بعملية جراحية دقيقة لتفكيك هذا الكلاس الضخم إلى عدة كلاسات أصغر، كل واحد منها له مسؤولية واحدة واضحة.
قبل التعديل: الكلاس “الشامل” الكارثي
هذا شكل مبسط للكلاس الذي كان لدينا:
// C# Example
public class UserService
{
public void RegisterUser(string email, string password)
{
// ... منطق تسجيل مستخدم جديد وتخزينه في الداتا بيس
}
public bool AuthenticateUser(string email, string password)
{
// ... منطق التحقق من الإيميل وكلمة المرور
// ... هذا الجزء تأثر بالتغيير بالخطأ
}
public UserReport GenerateUserActivityReport(int userId)
{
// ... منطق جلب البيانات وتجميع التقرير
}
public void ExportReportToPDF(UserReport report)
{
// ... منطق تحويل التقرير إلى ملف PDF
// ... هنا كان التعديل الذي سبب المشكلة
}
}
كما ترون، الكلاس يفعل كل شيء. إنه “الرجل الخارق” الذي سرعان ما يتحول إلى “الرجل المأساوي”.
بعد التعديل: جيش من الكلاسات المتخصصة
قمنا بتقسيم المسؤوليات على النحو التالي:
1. كلاس لإدارة المستخدمين (User Management)
مسؤوليته فقط هي التعامل مع بيانات المستخدم الأساسية.
public class UserAccountService
{
public void RegisterUser(string email, string password)
{
// ... منطق تسجيل مستخدم جديد
}
}
2. كلاس لعملية المصادقة (Authentication)
مسؤوليته الوحيدة هي التحقق من هوية المستخدم. سبب تغييره الوحيد هو لو تغيرت سياسة الأمان (مثلاً إضافة 2FA).
public class AuthenticationService
{
public bool AuthenticateUser(string email, string password)
{
// ... منطق التحقق من الإيميل وكلمة المرور
}
}
3. كلاس لإنشاء التقارير (Reporting)
مسؤوليته هي جمع البيانات وتنسيقها لإنشاء تقرير. سبب تغييره هو لو طلب العميل إضافة معلومات جديدة للتقرير.
public class ReportGeneratorService
{
public UserReport GenerateUserActivityReport(int userId)
{
// ... منطق جلب البيانات وتجميع التقرير
// ... هنا يمكننا تعديل تنسيق التاريخ بأمان
}
}
4. كلاس لتصدير التقارير (Exporting)
مسؤوليته هي أخذ تقرير جاهز وتحويله لصيغة معينة. سبب تغييره هو لو أردنا إضافة صيغة تصدير جديدة (مثل Excel).
public class PdfExportService
{
public void ExportReportToPDF(UserReport report)
{
// ... منطق تحويل التقرير إلى ملف PDF
}
}
الآن، لو طُلب منا تغيير تنسيق التاريخ مرة أخرى، سنذهب مباشرة إلى `ReportGeneratorService` ونعدل عليه ونحن واثقون تماماً أننا لن نكسر نظام تسجيل الدخول أو أي شيء آخر. كل جزء أصبح مستقلاً، سهل الفهم، سهل الاختبار، وآمن للتعديل.
نصائح أبو عمر الذهبية لتطبيق المبدأ
- فكر بـ “الأسباب” وليس “المهام”: قد يحتوي الكلاس على عدة دوال (methods)، وهذا طبيعي. السؤال الأهم هو: هل كل هذه الدوال تخدم “سبباً واحداً للتغيير”؟ كلاس `Calculator` قد يحتوي على `Add`, `Subtract`, `Multiply`، وكلها تخدم سبباً واحداً وهو “تغيير في العمليات الحسابية الأساسية”.
- اسأل حالك: “مين اللي ممكن يطلب تغيير هاد الكلاس؟”: إذا كان الجواب “فريق الأمان” و “فريق تحليل البيانات”، فغالباً أنت تخرق المبدأ. كل “جهة” أو “قسم” يجب أن يتعامل مع كلاسات مختلفة.
- لا تبالغ في التجزئة: الهدف ليس إنشاء كلاس لكل سطر كود! المبالغة في التقسيم قد تعقّد الأمور أكثر. ابحث عن التوازن المنطقي. إذا كان لديك كلاس صغير جداً ومنطقي أن يكون مع كلاس آخر، فلا بأس بدمجهما طالما يخدمان نفس “السبب”.
- إعادة الهيكلة (Refactoring) ليست رفاهية: تخصيص وقت لإعادة هيكلة الكود القديم وتطبيق مبادئ مثل SRP ليس وقتاً ضائعاً، بل هو استثمار يمنع كوارث مستقبلية ويوفر عليك ليالٍ طويلة من تصحيح الأخطاء.
الخلاصة: من الفوضى إلى النظام 🧘
مبدأ المسؤولية الواحدة ليس مجرد قاعدة أكاديمية نقرأها في الكتب. إنه أداة عملية للبقاء على قيد الحياة في عالم تطوير البرمجيات المعقد. يحول الكود من صحن سباغيتي متشابك إلى قطع ليغو منظمة، يمكنك إعادة ترتيبها وتشكيلها بسهولة وأمان.
في المرة القادمة التي تكتب فيها كلاس جديداً، توقف للحظة واسأل نفسك: “ما هي مسؤولية هذا الكلاس؟ هل هي حقاً مسؤولية واحدة؟”. هذا السؤال البسيط قد ينقذك وينقذ فريقك من جحيم مشابه لذلك الذي مررنا به.
تذكر دائماً يا صديقي المبرمج: اكتب الكود اليوم وكأنك ستعود لقراءته بعد سنة وأنت ناسي كل إشي عنه. اجعله بسيطاً، واضحاً، وذا مسؤولية واحدة. وصدقني، “أنت” المستقبلي سيشكرك كثيراً. 👍