Import cycle not allowed: عندما تضع Go حاجزًا في مسار الكود
\n\nكل مطور Go يواجه عاجلاً أم آجلاً هذه العبارة. يقطع المترجم عملية البناء فجأة، مصدرًا رسالة جافة: import cycle not allowed. يبدو أن البرنامج منطقي والحزم مترابطة "كما ينبغي". لكن Go ترى غير ذلك.
هذا الخطأ ليس نزوة من اللغة، بل حماية من الفوضى. التبعيات الدائرية تحول الكود إلى فوضى عارمة، وتعقد الاختبار، وتجعل عملية البناء غير متوقعة. في هذه المقالة، سنشرح سبب حدوث import cycle not allowed، ونقارن بين ثلاث طرق لحلها: من خلال الواجهات، ومن خلال الحزمة الوسيطة، ومن خلال إعادة هيكلة الكود. ستتعرف على الطريقة المناسبة حسب الموقف.
\n\nلماذا تمنع Go الاستيراد الدائري؟
\n\nعلى عكس اللغات ذات التحميل الديناميكي (Python, JavaScript)، يتم تجميع Go بشكل ثابت. يبني المترجم رسمًا بيانيًا لتبعيات الحزم مرة واحدة. إذا كانت الحزمة A تستورد الحزمة B، و B تستورد A - يحدث تكرار دائري. لا تستطيع Go حل هذا الترتيب، لذا توقف عملية التجميع.
\n\nمثال من الواقع: حزمة users تتعامل مع المستخدمين، حزمة orders تتعامل مع الطلبات. في orders نحتاج معلومات عن المستخدم، وفي users نحتاج قائمة الطلبات. النتيجة: import cycle not allowed.
سيناريو نموذجي: حزمتان "تتآلفان" بشكل وثيق جدًا
\n\nلننظر إلى موقف كلاسيكي. لدينا حزمتان: users و notifier.
حزمة users (ملف users.go):
package users\n\nimport "notifier"\n\ntype User struct {\n Name string\n Email string\n}\n\nfunc (u *User) SendWelcome() {\n notifier.Notify(u.Email, "Welcome!")\n}\n\n\nحزمة notifier (ملف notifier.go):
package notifier\n\nimport "users"\n\nfunc Notify(email, msg string) {\n // إرسال إشعار\n}\n\nfunc GetUserEmail(user *users.User) string {\n return user.Email\n}\n\n\nسيصدر المترجم: import cycle not allowed: users -> notifier -> users. كلتا الحزمتين تحاولان استيراد بعضهما البعض. ما الحل؟
الطريقة 1: الواجهات - "فرق تسد"
\n\nالطريقة الأكثر أناقة هي نقل التبعية إلى مستوى الواجهة. Go تحب العقود الصريحة.
\n\nننشئ حزمة contracts، حيث نحدد واجهة Notifier:
// contracts/contracts.go\npackage contracts\n\ntype Notifier interface {\n Notify(email, msg string)\n}\n\n\nالآن users لا تعتمد على notifier بشكل مباشر:
// users/users.go\npackage users\n\nimport "contracts"\n\ntype User struct {\n Name string\n Email string\n}\n\nfunc (u *User) SendWelcome(n contracts.Notifier) {\n n.Notify(u.Email, "Welcome!")\n}\n\n\nو notifier تنفذ الواجهة:
// notifier/notifier.go\npackage notifier\n\ntype EmailNotifier struct{}\n\nfunc (e *EmailNotifier) Notify(email, msg string) {\n // منطق الإرسال\n}\n\n\nالمزايا:
\n- \n
- غياب تام للاستيراد الدائري. \n
- سهولة الاختبار: يمكن استبدال التنفيذ. \n
- اقتران ضعيف (loose coupling). \n
العيوب:
\n- \n
- يتطلب حزمة إضافية للواجهات. \n
- أحيانًا لا تكون الواجهات ضرورية للمشاريع البسيطة. \n
الطريقة 2: الحزمة الوسيطة (Mediator)
\n\nإذا بدت الواجهات زائدة عن الحاجة، يمكن إنشاء حزمة ثالثة تكون "جسرًا" بين الحزمتين المتعارضتين.
\n\nننشئ حزمة bridge:
// bridge/bridge.go\npackage bridge\n\nimport (\n "notifier"\n "users"\n)\n\nfunc SendWelcomeToUser(user *users.User) {\n