كانت تبعيات مهامنا كابوساً لا ينتهي: كيف أنقذنا ‘الفرز الطوبولوجي’ من جحيم التنفيذ العشوائي؟

يا جماعة، السلام عليكم ورحمة الله وبركاته.

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

كنا نعمل على نظام معالجة بيانات ضخم، يتكون من عشرات المهام (Tasks) التي يجب أن تعمل بتسلسل معين. مهمة تنظف البيانات، وأخرى تستخلص السمات، وثالثة تدرب النموذج، ورابعة تقوم بالتقييم… وهكذا. كل مهمة تعتمد على مخرجات مهمة أو أكثر قبلها. في البداية، كنا ندير هذا التسلسل يدوياً. كان أحد المطورين يصرخ في القناة العامة على “سلاك”: “يا شباب، مهمة تنظيف البيانات جاهزة، مين يشغل مهمة استخلاص السمات؟”.

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

وهنا، بعد ليلة طويلة من البحث والتفكير، لمع في ذهني مصطلح كنت قد درسته في الجامعة ونسيته تقريباً: “الفرز الطوبولوجي” (Topological Sort). كانت تلك هي اللحظة التي بدأ فيها كل شيء يتغير.

ما هو الجحيم الذي كنا نعيشه؟ (فوضى التبعيات)

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

  • أنظمة البناء (Build Systems): لا يمكنك بناء البرنامج النهائي قبل تجميع (compile) كل وحداته (modules) التي يعتمد عليها.
  • مديرو الحزم (Package Managers): عند تثبيت مكتبة برمجية (مثل requests في بايثون)، يجب على مدير الحزم تثبيت تبعياتها أولاً (مثل urllib3).
  • خطوط أنابيب البيانات (Data Pipelines): كما في قصتنا، يجب معالجة البيانات الأولية قبل تجميعها، ويجب تجميعها قبل تدريب النموذج عليها.
  • جداول البيانات: الخلية C1 التي تحتوي على المعادلة =A1+B1 تعتمد على قيم الخلايا A1 و B1.

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

المنقذ: الفرز الطوبولوجي (Topological Sort) ببساطة

الفرز الطوبولوجي ليس خوارزمية فرز بالمعنى التقليدي (مثل فرز الأرقام من الأصغر للأكبر). بل هو خوارزمية لترتيب عقد (nodes) في “مخطط موجّه غير حلقي” بشكل خطي.

لحظة، ما كل هذه المصطلحات؟ دعنا نبسطها.

شروط اللعبة: ما هو المخطط الموجه غير الحلقي (DAG)؟

لنفهم الفرز الطوبولوجي، يجب أن نمثل مشكلتنا على شكل مخطط (Graph). هذا المخطط له ثلاث صفات أساسية:

  1. المخطط (Graph): هو ببساطة مجموعة من “العقد” (النقاط) و “الحواف” (الخطوط التي تصل بينها). في حالتنا، كل “مهمة” هي عقدة.
  2. موجه (Directed): كل حافة لها اتجاه (يتم تمثيلها بسهم). إذا كانت المهمة “ب” تعتمد على المهمة “أ”، نرسم سهماً من “أ” إلى “ب” (أ ← ب). هذا يعني “يجب إكمال ‘أ’ قبل البدء في ‘ب'”.
  3. غير حلقي (Acyclic): هذا هو الشرط الأهم. يعني أنه لا يمكنك أن تبدأ من عقدة وتتبع الأسهم وتعود إلى نفس العقدة مرة أخرى. فكّر فيها منطقياً: لو كانت المهمة “أ” تعتمد على “ب”، و “ب” تعتمد على “أ”، فمتى ستبدأ؟ هذه حلقة مفرغة ومستحيلة التنفيذ. نسمي هذا المخطط اختصاراً DAG (Directed Acyclic Graph).

إذن، الفرز الطوبولوجي يأخذ هذا الـ DAG ويعطينا قائمة مرتبة من المهام تضمن أنه عند تنفيذها بهذا الترتيب، ستكون كل تبعيات المهمة الحالية قد اكتملت بالفعل.

كيف يعمل الفرز الطوبولوجي؟ (الخوارزمية خطوة بخطوة)

هناك عدة طرق لتنفيذ الفرز الطوبولوجي، لكن أشهرها وأكثرها بديهية هي “خوارزمية كان” (Kahn’s Algorithm). لنشرحها بأسلوب عملي وبسيط.

خوارزمية كان (Kahn’s Algorithm): الطريقة العملية

تخيل أنك مدير لمجموعة من العمال (المهام). بعض العمال لا يمكنهم البدء في عملهم إلا بعد أن ينتهي عمال آخرون.

  1. الخطوة الأولى: حساب “الدرجة الواردة” (In-degree)
    لكل مهمة (عقدة)، قم بحساب عدد المهام التي تعتمد عليها مباشرة. أي، كم عدد الأسهم التي تشير إلى هذه العقدة؟ هذا الرقم يسمى “الدرجة الواردة”.
  2. الخطوة الثانية: تحديد نقطة البداية
    ابحث عن جميع المهام التي لها درجة واردة تساوي صفر. هذه هي المهام “المستقلة” التي لا تعتمد على أي شيء آخر. ضعها في قائمة انتظار (Queue) للبدء بها.
  3. الخطوة الثالثة: المعالجة والترتيب
    طالما أن قائمة الانتظار ليست فارغة، قم بما يلي:
    • أخرج مهمة من قائمة الانتظار. هذه هي المهمة التالية التي سيتم تنفيذها. أضفها إلى قائمتك النهائية المرتبة.
    • الآن، اعتبر هذه المهمة قد “اكتملت”. انظر إلى جميع المهام التي كانت تعتمد عليها (أي، جميع العقد التي يخرج منها سهم من هذه العقدة).
    • لكل مهمة “تابعة” من هذه المهام، قم بإنقاص “الدرجة الواردة” الخاصة بها بمقدار واحد (لأن إحدى تبعياتها قد اكتملت للتو).
    • إذا أصبحت الدرجة الواردة لأي من هذه المهام التابعة تساوي صفرًا، فهذا يعني أن جميع تبعياتها قد اكتملت الآن. أضفها إلى قائمة الانتظار.
  4. الخطوة الرابعة: التحقق من النتيجة
    استمر في تكرار الخطوة 3 حتى تفرغ قائمة الانتظار. في النهاية، إذا كان عدد المهام في قائمتك النهائية المرتبة يساوي العدد الإجمالي للمهام، فقد نجحت! لديك ترتيب طوبولوجي صحيح. أما إذا كان العدد أقل، فهذا يعني وجود “حلقة” (cycle) في مخططك، ولا يمكن حلها.

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

لنكتب بعض الكود: تطبيق الفرز الطوبولوجي بلغة Python

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

لنفترض أن لدينا المهام التالية وتبعياتها:

  • `إعداد قاعدة البيانات` (لا تبعيات)
  • `تجميع الواجهات` (لا تبعيات)
  • `بناء الواجهة الخلفية` (تعتمد على `إعداد قاعدة البيانات`)
  • `تشغيل الاختبارات` (تعتمد على `بناء الواجهة الخلفية`)
  • `نشر التطبيق` (تعتمد على `تشغيل الاختبارات` و `تجميع الواجهات`)

from collections import defaultdict, deque

def topological_sort(tasks, dependencies):
    """
    يقوم بتنفيذ الفرز الطوبولوجي باستخدام خوارزمية كان
    :param tasks: قائمة بجميع المهام (العقد)
    :param dependencies: قائمة من الأزواج (أ, ب) حيث ب تعتمد على أ
    :return: قائمة بالمهام مرتبة طوبولوجياً أو رسالة خطأ في حال وجود حلقة
    """
    # 1. بناء المخطط وحساب الدرجة الواردة (in-degree)
    graph = defaultdict(list)
    in_degree = {task: 0 for task in tasks}

    for prereq, task in dependencies:
        graph[prereq].append(task)
        in_degree[task] += 1

    # 2. إضافة العقد التي ليس لها تبعيات (in-degree = 0) إلى قائمة الانتظار
    queue = deque([task for task in tasks if in_degree[task] == 0])
    
    sorted_order = []

    # 3. المعالجة والترتيب
    while queue:
        current_task = queue.popleft()
        sorted_order.append(current_task)

        # إنقاص درجة الجيران وإضافتهم إلى قائمة الانتظار إذا أصبحت درجتهم صفر
        for neighbor in graph[current_task]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    # 4. التحقق من وجود حلقات
    if len(sorted_order) == len(tasks):
        return sorted_order
    else:
        return "خطأ: توجد حلقة (cycle) في التبعيات ولا يمكن إيجاد ترتيب صحيح."

# تعريف المهام والتبعيات لمثالنا
tasks = [
    "إعداد قاعدة البيانات", 
    "تجميع الواجهات", 
    "بناء الواجهة الخلفية", 
    "تشغيل الاختبارات", 
    "نشر التطبيق"
]
dependencies = [
    ("إعداد قاعدة البيانات", "بناء الواجهة الخلفية"),
    ("بناء الواجهة الخلفية", "تشغيل الاختبارات"),
    ("تشغيل الاختبارات", "نشر التطبيق"),
    ("تجميع الواجهات", "نشر التطبيق")
]

# تنفيذ الفرز
sorted_tasks = topological_sort(tasks, dependencies)

print("الترتيب الصحيح لتنفيذ المهام:")
print(sorted_tasks)

# مثال على إحدى النتائج الممكنة:
# ['إعداد قاعدة البيانات', 'تجميع الواجهات', 'بناء الواجهة الخلفية', 'تشغيل الاختبارات', 'نشر التطبيق']

لاحظ أن هناك ترتيباً آخر ممكناً وصحيحاً أيضاً: ['تجميع الواجهات', 'إعداد قاعدة البيانات', ...] لأن كلتا المهمتين ليس لهما تبعيات ويمكن البدء بأي منهما.

نصائح من خبرة أبو عمر 🧔

  • ليس هناك حل واحد فريد دائماً: كما رأينا في المثال، قد يكون هناك أكثر من ترتيب طوبولوجي صحيح. الخوارزمية ستعطيك واحداً منها. المهم هو أن أي ترتيب تنتجه سيكون صحيحاً ويحترم جميع التبعيات.
  • الأداء مهم في المشاريع الكبيرة: تعقيد هذه الخوارزمية هو O(V + E) حيث V هو عدد العقد (المهام) و E هو عدد الحواف (التبعيات). هذا يعني أنها سريعة جداً وفعالة حتى مع آلاف المهام.
  • أبعد من مجرد كود: الفرز الطوبولوجي هو نموذج عقلي قوي. يمكنك استخدامه لتخطيط أي مشروع معقد، سواء كان ذلك إعداد وجبة عشاء من عدة أطباق، أو تنظيم حفل زفاف، أو إدارة مراحل مشروع بناء. فكّر في المهام والتبعيات، وستجد أنك ترسم مخطط DAG في عقلك.
  • استخدم الأدوات الجاهزة: معظم أنظمة إدارة المهام وخطوط أنابيب البيانات الحديثة (مثل Apache Airflow, Luigi, dbt) تستخدم الفرز الطوبولوجي خلف الكواليس. فهمك لهذه الخوارزمية سيجعلك تستخدم هذه الأدوات بفعالية أكبر بكثير.

الخلاصة: من الفوضى إلى النظام ✅

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

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

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

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

تجربة المستخدم والابداع البصري

كانت تصاميمنا تتحطم عند التسليم: كيف أنقذتنا ‘رموز التصميم’ (Design Tokens) من جحيم الهوة بين المصمم والمطور؟

أشارككم قصة حقيقية عن الفوضى التي كانت تعم مشاريعنا بسبب الفجوة بين التصميم والتنفيذ. اكتشفوا كيف كانت "رموز التصميم" (Design Tokens) هي الجسر الذي أنقذنا،...

31 مايو، 2026 قراءة المزيد
برمجة وقواعد بيانات

كان تحديث قاعدة البيانات يوقف خدماتنا: كيف أنقذتنا استراتيجيات الترحيل بدون توقف (Zero-Downtime Migration) من جحيم نافذة الصيانة؟

أشارككم قصة ليلة طويلة تعلمت فيها بالطريقة الصعبة أن "نافذة الصيانة" هي عدو للمستخدمين والشركات. نستكشف معاً استراتيجيات الترحيل بدون توقف (Zero-Downtime Migration) التي تحافظ...

31 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كان حسابي على GitHub مقبرة للمشاريع الميتة: كيف أنقذتني ‘المساهمات المفتوحة المصدر’ من جحيم السيرة الذاتية الفارغة؟

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

31 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كان فشل خدمة واحدة يُسقط نظامنا بأكمله: كيف أنقذنا نمط ‘قاطع الدائرة’ من جحيم الفشل المتتالي؟

أتذكر ليلة كادت فيها خدمة واحدة أن تدمر مشروعنا بالكامل بسبب الفشل المتتالي. في هذه المقالة، أشارككم قصة كيف أنقذنا نمط 'قاطع الدائرة' (Circuit Breaker)،...

31 مايو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

كانت أرصدتنا تتبخر في الهواء: كيف أنقذنا ‘دفتر الأستاذ المزدوج’ من جحيم التسويات اليدوية؟

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

31 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كانت أسرارنا تتسرب من كل مكان: كيف أنقذتنا ‘إدارة الأسرار المركزية’ من كابوس المفاتيح المسروقة؟

أشارككم قصة حقيقية عن كابوس أمني كاد أن يدمر مشروعنا، وكيف كانت "إدارة الأسرار المركزية" طوق النجاة. اكتشفوا معنا كيف تحمون مفاتيحكم الرقمية وتنتقلون من...

31 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كان الخوف من الفشل يشلّ فريقنا: كيف أنقذتنا ‘السلامة النفسية’ من جحيم الأفكار التي لم تولد قط؟

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

31 مايو، 2026 قراءة المزيد
البودكاست