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

يا مساء الخير يا جماعة، معكم أخوكم أبو عمر.

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

المهام صارت تاخد وقت أطول من اللازم، وبعدين تفشل بسبب انتهاء المهلة (Timeout). حاولنا نعيد تشغيلها، نفس المشكلة. قعدنا يومين كاملين، والفريق كله على أعصابه. واحد بقول المشكلة من الشبكة، والثاني بقول في تغيير جديد عمله فلان، والكل صار يرمي التهم على بعض. الأجواء كانت مشحونة، والقهوة ما عادت تعمل مفعولها.

في ليلة من الليالي، وأنا براجع سجلات التنفيذ (Logs) للمرة المليون، لاحظت نمط غريب: المهمة A تبدأ، ثم تنتظر المهمة B. المهمة B تبدأ، وتنتظر المهمة C. والمهمة C… كانت تنتظر المهمة A! وقتها صرخت (بيني وبين حالي طبعاً): “لقيتها! هاي اعتمادية دائرية (Circular Dependency)!”. مهامنا كانت بتلف وبتدور في حلقة مفرغة، كل مهمة بتستنى اللي قبلها تخلص، وما حدا راح يخلص. هون بالزبط تذكرت صديق قديم اسمه “الفرز الطوبولوجي”، وعرفت إنه هو المنقذ الوحيد من هالجحيم.

ما هو الجحيم الذي كنا فيه؟ (فهم الاعتماديات)

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

بنقدر نمثل هاي الاعتماديات على شكل “مخطط موجّه” أو (Directed Graph). كل مهمة هي “عقدة” (Node)، وكل اعتمادية هي “سهم” (Edge) من مهمة لأخرى. فإذا كانت المهمة A تعتمد على المهمة B، بنرسم سهم من B إلى A (يعني B لازم تخلص قبل A).

مثال بسيط: تحضير الشاي

  • غلي الماء (مهمة 1)
  • وضع الشاي في الكوب (مهمة 2)
  • صب الماء المغلي فوق الشاي (مهمة 3)

الاعتماديات: المهمة 3 تعتمد على 1 و 2. يعني لازم 1 و 2 يخلصوا قبل ما تبدأ 3.

المشكلة بتصير لما هاي الأسهم تشكّل “دائرة مغلقة”. يعني A تعتمد على B، و B تعتمد على C، و C ترجع تعتمد على A. هذا اسمه “اعتمادية دائرية” (Circular Dependency)، وهو وضع مستحيل منطقياً. كيف تبدأ A إذا كانت تنتظر C، وC تنتظر B، وB تنتظر A؟ هذا بالزبط اللي صار معنا.

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

الفرز الطوبولوجي هو ببساطة عملية إيجاد ترتيب خطي لجميع العقد (المهام) في مخطط موجّه، بحيث لو كان في سهم من العقدة U إلى العقدة V، فإن U تأتي دائماً قبل V في الترتيب.

شو يعني هالحكي؟ يعني هو خوارزمية بتعطينا الترتيب الصحيح والمنطقي لتنفيذ المهام. لو طبقنا الفرز الطوبولوجي على مثال الشاي، ممكن يعطينا الترتيب: [غلي الماء، وضع الشاي، صب الماء] أو [وضع الشاي، غلي الماء، صب الماء]. المهم إن المهمة الأخيرة (صب الماء) تيجي بعد المهمتين الأولانيات.

والأهم من هذا كله: الفرز الطوبولوجي لا يمكن تطبيقه إلا على مخطط موجّه لا يحتوي على دوائر (Directed Acyclic Graph – DAG). إذا حاولت تطبقه على مخطط فيه دائرة، الخوارزمية راح تكتشف هاي الدائرة وتخبرك عنها. وهذا هو السلاح السري اللي كنا محتاجينه!

كيف يعمل الفرز الطوبولوجي؟ (خوارزمية كان – Kahn’s Algorithm)

واحدة من أشهر الطرق لعمل الفرز الطوبولوجي هي خوارزمية “كان”. فكرتها بسيطة ومنطقية جداً، وبتشبه الطريقة اللي بنفكر فيها طبيعياً:

  1. ابحث عن المهام التي لا تعتمد على أي شيء: في مخططنا، هاي هي العقد اللي ما بوصلها أي سهم. بنسمي عدد الأسهم اللي بتدخل على العقدة “الدرجة الداخلية” (In-degree). فإحنا بندور على العقد اللي درجتها الداخلية تساوي صفر.
  2. نفّذ هذه المهام: بما إنها ما بتعتمد على إشي، بنقدر نبدأ فيها. بنحطها في قائمة انتظار (Queue).
  3. حدّث الاعتماديات: كل ما نخلص مهمة من قائمة الانتظار، بنعتبر إنها “انتهت”. بنروح على كل المهام اللي كانت تعتمد عليها (جيرانها) وبنقلل درجة اعتماديتها (in-degree) بمقدار واحد. كأننا بنقول: “يا مهمة X، المهمة Y اللي كنتي تستنيها خلصت، هسا ناقصك اعتمادية واحدة أقل”.
  4. أضف المهام الجديدة الجاهزة: إذا أي مهمة من الجيران صارت درجة اعتماديتها صفر، معناها صارت جاهزة للتنفيذ. بنضيفها على طول على قائمة الانتظار.
  5. كرر العملية: بنضل نكرر الخطوات 3 و 4 حتى تفضى قائمة الانتظار.

الترتيب اللي بنطلع فيه المهام من قائمة الانتظار هو نفسه الترتيب الطوبولوجي!

وماذا عن الدوائر؟

الجميل في خوارزمية كان إنها بتكشف الدوائر تلقائياً. كيف؟ بعد ما تخلص الخوارزمية، بنقارن عدد المهام اللي ترتبت معنا بعدد المهام الكلي. إذا كان العدد أقل، فهذا يعني أن هناك مهام لم تدخل الترتيب أبداً. ليش؟ لأنها ظلت عالقة في دائرة، ودرجتها الداخلية (in-degree) لم تصل إلى الصفر أبداً. وبهيك بنكون كشفنا المشكلة وعرفنا مكانها.

مثال برمجي بلغة Python

الحكي النظري حلو، بس الكود أحلى. هاي نسخة مبسطة من خوارزمية كان باستخدام Python:


from collections import deque

def topological_sort(graph):
    # graph هو قاموس يمثل المخطط
    # المفتاح هو العقدة، والقيمة هي قائمة بالعقد التي تعتمد عليها
    # مثال: {'C': ['A', 'B']} تعني C تعتمد على A و B
    
    # 1. حساب الدرجة الداخلية (in-degree) لكل عقدة
    in_degree = {node: 0 for node in graph}
    # بناء المخطط العكسي لتسهيل الوصول للجيران
    adj = {node: [] for node in graph}
    
    for node, dependencies in graph.items():
        for dep in dependencies:
            adj[dep].append(node)
            in_degree[node] += 1
            
    # 2. إنشاء قائمة انتظار ووضع العقد التي درجتها صفر
    queue = deque([node for node in graph if in_degree[node] == 0])
    
    sorted_order = []
    
    # 3. بدء المعالجة
    while queue:
        node = queue.popleft()
        sorted_order.append(node)
        
        # 4. تحديث الجيران
        if node in adj:
            for neighbor in adj[node]:
                in_degree[neighbor] -= 1
                # 5. إضافة الجيران الجاهزين
                if in_degree[neighbor] == 0:
                    queue.append(neighbor)
                    
    # 6. التحقق من وجود دائرة
    if len(sorted_order) == len(graph):
        return sorted_order # نجح الفرز
    else:
        # يمكن هنا إضافة منطق لإيجاد العقد التي في الدائرة
        return "Error: Graph has a cycle!"

# --- مثال الاستخدام ---
# مهامنا ومين بيعتمد على مين
tasks = {
    'build_app': ['install_deps'],
    'install_deps': [],
    'run_tests': ['build_app'],
    'deploy': ['run_tests', 'push_image'],
    'push_image': ['build_app']
}

# لو أضفنا اعتمادية دائرية
# tasks_with_cycle = tasks.copy()
# tasks_with_cycle['install_deps'].append('deploy') # install_deps تعتمد على deploy -> دائرة!

sorted_tasks = topological_sort(tasks)
print("ترتيب التنفيذ الصحيح:", sorted_tasks)
# الناتج المتوقع: ['install_deps', 'build_app', 'push_image', 'run_tests', 'deploy'] (أو ترتيب آخر صحيح)

# sorted_tasks_cycle = topological_sort(tasks_with_cycle)
# print(sorted_tasks_cycle) # الناتج: Error: Graph has a cycle!

نصائح عملية من أخوكم أبو عمر

بعد ما حلينا المشكلة بفضل الله ثم الفرز الطوبولوجي، تعلمت شوية دروس بحب أشاركها معكم:

  • فكّر بالرسوم البيانية (Graphs): كثير من المشاكل في البرمجة، خصوصاً اللي فيها علاقات وترتيب، يمكن تبسيطها لو فكرت فيها على شكل عقد وأسهم. ارسمها على ورقة أو على سبورة، بتلاقي الحل صار أوضح.
  • التحقق من الدوائر هو الأهم: لا تكتفِ بتطبيق الفرز. الأهم هو بناء آلية قوية لاكتشاف الدوائر والإبلاغ عنها بشكل واضح. بدل ما تطبع رسالة خطأ عامة، حاول تطبع مسار الدائرة نفسه (مثلاً: A -> B -> C -> A). هذا بيوفر ساعات من التنقيب والبحث على فريقك.
  • اجعل الاعتماديات واضحة: في مشاريعك، حاول يكون ملف الإعدادات أو الكود اللي بيعرف الاعتماديات واضح ومقروء. كل ما كان النظام غامض، زادت فرصة حدوث أخطاء مثل الاعتماديات الدائرية.
  • هناك طرق أخرى: خوارزمية كان ممتازة، ولكن هناك طريقة أخرى مشهورة تعتمد على البحث بالعمق أولاً (Depth-First Search – DFS). هذه الطريقة قوية جداً أيضاً في اكتشاف الدوائر. تعلم الطريقتين واختر الأنسب لمشكلتك.

الخلاصة: النظام أساس كل شيء

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

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

يلا، شدّوا حيلكم ورجوني إبداعاتكم! 💪

أبو عمر

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

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

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

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

آخر المدونات

التكنلوجيا المالية Fintech

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

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

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

ميزانيات الخطأ (Error Budgets): كيف أنهت كابوس مكالمات منتصف الليل وأنقذتنا من الإرهاق؟

كنا غارقين في مكالمات طوارئ ليلية لا تنتهي، فريق منهك والمنتج على المحك. في هذه المقالة، أشارككم قصة كيف أنقذنا مفهوم "ميزانيات الخطأ" (Error Budgets)...

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

كانت اجتماعاتنا الفردية استجواباً صامتاً: كيف حولنا الـ 1-on-1 من تقرير حالة ممل إلى محرك لنمو الفريق؟

أشارككم تجربتي كقائد فريق تقني في تحويل الاجتماعات الفردية (1-on-1s) من جلسات استجواب مملة إلى محادثات مثمرة تساهم في بناء الثقة وتطوير الفريق. هذه المقالة...

30 مايو، 2026 قراءة المزيد
اختبارات الاداء والجودة

كانت اختباراتنا تصرخ ‘الذئب’: كيف قضينا على ‘الاختبارات المتقلبة’ (Flaky Tests) واستعدنا الثقة في خطوط الأنابيب؟

في هذه المقالة، أشارككم قصة من أرض المعركة البرمجية، وكيف تغلب فريقي على كابوس "الاختبارات المتقلبة" أو Flaky Tests. سنغوص في أسبابها الخفية، ونتعلم استراتيجيات...

30 مايو، 2026 قراءة المزيد
أدوات وانتاجية

كانت أصابعي تصرخ من التكرار: كيف أنقذتني ‘مقتطفات الشفرة’ (Code Snippets) من جحيم كتابة Boilerplate؟

أشارككم قصتي مع التكرار الممل في البرمجة وكيف غيرت "مقتطفات الشفرة" (Code Snippets) طريقة عملي تماماً. دليل عملي من مبرمج فلسطيني لزيادة إنتاجيتك والتخلص من...

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

كانت تبعياتنا قنبلة موقوتة: كيف أنقذنا ‘التحديث الآلي للتبعيات’ من جحيم الثغرات الأمنية المنسية؟

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

30 مايو، 2026 قراءة المزيد
نصائح برمجية

كانت شفرتنا هرمًا من الهلاك: كيف أنقذتنا ‘شروط الحماية’ (Guard Clauses) من جحيم الـ if/else المتداخلة؟

في هذه المقالة، أشارككم قصة حقيقية من مسيرتي كمبرمج عن المعاناة مع الشفرات المتداخلة "هرم الهلاك". سنتعلم كيف تنقذنا تقنية "شروط الحماية" (Guard Clauses) من...

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

كانت خدماتنا متلاصقة كالغراء: كيف أنقذتنا ‘المعمارية الموجهة بالأحداث’ (EDA) من جحيم الاقتران المحكم؟

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

30 مايو، 2026 قراءة المزيد
ذكاء اصطناعي

كانت نماذجنا تموت ببطء: كيف أنقذنا “انحراف النموذج” (Model Drift) من جحيم التنبؤات الفاسدة؟

في عالم الذكاء الاصطناعي، نماذجنا ليست منحوتات حجرية، بل كائنات حية تتنفس البيانات. أشارككم قصة حقيقية عن "انحراف النموذج" (Model Drift)، هذا الشبح الذي كاد...

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