بأتذكرها زي كأنه امبارح. كنا شغالين على ميزة جديدة في نظامنا، ميزة أساسية ومهمة جداً، والضغط علينا كان كبير عشان نسلمها قبل نهاية الربع السنوي. أنا وزميلي “أحمد” أخذنا المهمة على عاتقنا. قعدنا نشتغل ليل نهار، نضيف كود، نعدل كود، ونبني طبقات فوق بعضها. بعد ثلاث أسابيع من القهوة والكود، أخيراً… خلصنا.
بكل فخر، فتحت طلب دمج (Pull Request) جديد. لكن لما شفت الأرقام، قلبي انقبض: “+7530 -4120” في 80 ملف. يعني طلب دمج بحجم ديناصور! رميته للفريق وقلت “تفضلوا يا شباب، جاهز للمراجعة”.
اللي صار بعد هيك كان متوقع. الفريق كله صار يتهرب من المراجعة. كل ما أسأل حدا، يقولي “مشغول والله يا أبو عمر، بس أفضى براجعه”. وبعد يومين من الصمت المطبق، بدأت التعليقات الخجولة تظهر: “LGTM” (يبدو جيداً لي)، “تعديل بسيط على اسم متغير هنا”، “ليش ما استخدمت X بدل Y؟”. ما حدا فعلاً غاص في أعماق الكود وفهم منطقه المعقد. بعد أسبوع من الدفع والجذب، تم دمج الكود على مضض.
والنتيجة؟ في الأسبوع اللي بعده، قضينا وقت في إصلاح الأخطاء (Bugs) أكثر من الوقت اللي قضيناه في بناء الميزة نفسها. أخطاء كانت واضحة زي عين الشمس، بس ضاعت في زحمة آلاف الأسطر. وقتها قلت لحالي: “خلص، لازم نلاقي طريقة أحسن. الشغل هيك ما بمشي”. ومن هنا بدأت رحلتنا مع ما يسمى بـ “التغييرات المكدسة” أو الـ Stacked Diffs.
لماذا طلبات الدمج الكبيرة هي وصفة للكارثة؟
قبل ما نحكي عن الحل، خلينا نفصّل المشكلة. القصة اللي حكيتها مش حالة نادرة، هي القاعدة في كثير من فرق البرمجة للأسف. طلبات الدمج العملاقة تسبب مشاكل متسلسلة:
- صعوبة المراجعة العقلية: الدماغ البشري لا يستطيع استيعاب ومراجعة آلاف التغييرات دفعة واحدة بكفاءة. بعد أول 200-300 سطر، يبدأ التركيز بالضياع، والمراجع يبدأ بالبحث عن الأخطاء السطحية فقط.
- التأخير في التسليم (Cycle Time): طلب دمج كبير يعني مراجعة طويلة، نقاشات لا تنتهي، وتعديلات كثيرة. هذا يبقي الكود “عالقا” في مرحلة المراجعة لأسابيع، ويمنع تسليم القيمة للمستخدم بسرعة.
- زيادة مخاطر الأخطاء: بما أن المراجعة سطحية، فمن الطبيعي أن تتسلل الأخطاء المنطقية والفنية إلى الكود الأساسي (main branch)، مما يؤدي إلى مشاكل في بيئة الإنتاج.
- جحيم صراعات الدمج (Merge Conflicts): كلما طالت مدة بقاء فرعك (branch) دون دمج، زاد ابتعاده عن الفرع الرئيسي. هذا يعني أنك ستقضي وقتاً طويلاً ومؤلماً في حل صراعات الدمج المعقدة.
الحل السحري: التغييرات المكدسة (Stacked Diffs)
الفكرة بسيطة بشكل عبقري: بدلاً من إنشاء طلب دمج واحد ضخم لميزة كاملة، قم بتجزئة عملك إلى مجموعة من التغييرات الصغيرة، المنطقية، والمستقلة، ثم قم بإنشاء طلب دمج لكل تغيير صغير، بحيث يعتمد كل طلب دمج على الذي قبله.
تخيل أنك تبني بيتاً من الليغو. بدلاً من أن تبني البيت كله سراً ثم تعرضه على أصدقائك ليعطوك رأيهم، أنت تعرض عليهم كل خطوة:
- “يا جماعة، هذا هو الأساس، شو رأيكم؟” (طلب دمج #1)
- “تمام، فوق الأساس بنيت الجدران، هل تبدو متينة؟” (طلب دمج #2 يعتمد على #1)
- “والآن وضعت السقف، هل هناك أي مشاكل؟” (طلب دمج #3 يعتمد على #2)
بهذه الطريقة، كل مراجعة تكون صغيرة، مركزة، وسهلة الهضم. والمراجع يستطيع تتبع تطور فكرك خطوة بخطوة.
في عالم Git، هذا يعني أنك تنشئ سلسلة من الفروع، حيث يكون الفرع
feature-part-2مبنياً علىfeature-part-1بدلاً منmain.
كيف نبدأ؟ دليل عملي خطوة بخطوة
الحكي حلو، بس كيف التطبيق العملي؟ خلينا نمشي خطوة بخطوة باستخدام Git. لنفترض أننا نريد بناء نفس الميزة الضخمة السابقة.
الخطوة الأولى: التخطيط والتجزئة
قبل كتابة سطر كود واحد، افتح محرر نصوص أو ورقة وقلم، وفكر: “كيف يمكنني تقسيم هذه الميزة إلى أجزاء منطقية ومستقلة قدر الإمكان؟”.
مثال لتقسيم ميزة “صفحة ملف المستخدم”:
- الجزء 1: تعديلات على قاعدة البيانات (إضافة جدول أو حقول جديدة).
- الجزء 2: إنشاء الواجهة البرمجية (API endpoint) لجلب بيانات المستخدم.
- الجزء 3: بناء مكون واجهة المستخدم (UI Component) لعرض الصورة الرمزية للمستخدم.
- الجزء 4: بناء مكون واجهة المستخدم لعرض معلومات المستخدم (الاسم، البريد الإلكتروني).
- الجزء 5: تجميع كل المكونات في صفحة واحدة وربطها بالـ API.
كل جزء من هذه الأجزاء يمكن أن يكون طلب دمج منفصل وصغير.
الخطوة الثانية: إنشاء الكومة (The Stack)
الآن لنطبق هذا باستخدام Git. سنبدأ دائماً من الفرع الرئيسي المحدث (main أو master).
# 1. ابدأ من الفرع الرئيسي وتأكد من أنه محدّث
git checkout main
git pull origin main
# 2. أنشئ الفرع الأول للجزء الأول من الميزة
git checkout -b abu-omar/user-profile-db
# ... اكتب الكود الخاص بقاعدة البيانات، ثم قم بعمل commit ...
git add .
git commit -m "feat: Add user_profile table to database"
# 3. ادفع الفرع وأنشئ طلب الدمج الأول (PR #1) الذي يستهدف main
git push -u origin abu-omar/user-profile-db
# 4. الآن، بدلاً من العودة لـ main، ابقَ في فرعك الحالي وأنشئ الفرع الثاني منه
git checkout -b abu-omar/user-profile-api
# ... اكتب الكود الخاص بالـ API، ثم قم بعمل commit ...
git add .
git commit -m "feat: Create API endpoint to fetch user profile"
# 5. ادفع الفرع الثاني وأنشئ طلب الدمج الثاني (PR #2) الذي يستهدف... abu-omar/user-profile-db
git push -u origin abu-omar/user-profile-api
وهكذا دواليك. كل فرع جديد ينبثق من الفرع الذي يسبقه في “الكومة”. عند فتح طلبات الدمج على GitHub أو GitLab، تأكد من تغيير الفرع المستهدف (base branch) لكل طلب دمج ليكون الفرع الذي يسبقه في السلسلة.
الخطوة الثالثة: إدارة التحديثات والمراجعات (الجزء الصعب)
هنا تكمن الصعوبة التي تخيف الكثيرين. ماذا لو طلب منك المراجع تغيير شيء في الجزء الأول (user-profile-db) بعد أن أصبحت تعمل على الجزء الثالث؟
الحل يكمن في صديقنا القوي والمخيف: git rebase.
# 1. عد إلى الفرع الأول الذي يحتاج إلى تعديل
git checkout abu-omar/user-profile-db
# 2. قم بالتعديلات المطلوبة. بدلاً من عمل commit جديد، قم بدمج التعديل مع الـ commit السابق
git add .
git commit --amend --no-edit
# 3. الآن عليك أن تدفع بالقوة، لكن استخدم --force-with-lease فهي أكثر أماناً
git push --force-with-lease
# 4. الآن الفروع الأخرى في الكومة أصبحت "قديمة". يجب تحديثها.
# اذهب إلى الفرع الثاني
git checkout abu-omar/user-profile-api
# 5. أعد بناء هذا الفرع فوق النسخة المحدثة من الفرع الأول
git rebase abu-omar/user-profile-db
# 6. ادفع التغييرات المحدثة للفرع الثاني
git push --force-with-lease
# 7. كرر العملية للفرع الثالث والرابع وهكذا...
git checkout abu-omar/user-profile-ui-avatar
git rebase abu-omar/user-profile-api
git push --force-with-lease
نعم، تبدو العملية معقدة في البداية، ولكنها تصبح عادة مع الممارسة. وهي التي تمنحك القوة الكاملة لهذه الطريقة.
أدوات لتسهيل حياتك
العمل اليدوي مع git rebase قد يكون مرهقاً وعرضة للخطأ. لحسن الحظ، هناك أدوات حديثة تم تصميمها خصيصاً لتبسيط هذه العملية:
- Graphite: أداة رائعة تحول سير العمل هذا إلى تجربة ممتعة. تتولى عنك كل تفاصيل الـ rebase والـ push المعقدة. تكتب
gt submitوهي تقوم بالباقي. - gh-stack: إضافة لـ GitHub CLI تساعد في إدارة “الكومة”.
- Sapling: نظام تحكم بالمصادر من Meta (فيسبوك سابقاً) مبني من الأساس حول فكرة التغييرات المكدسة.
نصائح من خبرة أبو عمر
- ابدأ صغيراً: لا تحاول بناء كومة من 10 طلبات دمج في أول مرة. جرب مع 2 أو 3 لتعرف كيف تسير الأمور.
- التواصل هو المفتاح: عندما تنشئ كومة من طلبات الدمج، اشرح في وصف كل طلب دمج علاقته بما قبله وما بعده. ضع روابط للطلبات الأخرى في السلسلة. هذا يساعد المراجعين على فهم الصورة الكاملة.
- أتمتة الفحوصات (CI/CD): تأكد من أن نظام الفحص والدمج المستمر (CI/CD) يعمل بشكل صحيح على كل طلب دمج في الكومة. هذا يعطي ثقة بأن كل جزء صغير يعمل بشكل سليم قبل الاعتماد عليه.
- لا تخف من الـ Rebase: تعلم الـ
git rebaseجيداً، فهو صديقك في هذه الرحلة. كثير من المبرمجين بخافوا منه، بس هو كنز للي بفهمه صح. استخدمه بحذر، ويفضل دائماً استخدام--force-with-leaseبدلاً من--force. - اجعل كل طلب دمج “قابلاً للدمج”: حاول أن يكون كل طلب دمج في الكومة يمثل حالة مستقرة وكاملة منطقياً. لا تترك الكود “مكسوراً” وتعتمد على الطلب التالي لإصلاحه.
الخلاصة: من الفوضى إلى الإنتاجية 🧘
الانتقال إلى “التغييرات المكدسة” كان نقلة نوعية لفريقنا. تحولنا من مراجعات مرهقة لا تنتهي، إلى دورة سريعة من التغييرات الصغيرة والمركزة. زادت سرعة التسليم، قلت الأخطاء بشكل ملحوظ، والأهم من كل ذلك، تحسنت جودة المراجعات لأن كل مراجع يتعامل مع جزء صغير يمكن استيعابه.
هذه الطريقة ليست مجرد خدعة تقنية، بل هي تغيير في العقلية. هي تدفعك للتفكير في مشاكلك بشكل مجزأ ومنظم قبل كتابة الكود. إنها تجبرك على بناء برمجياتك كقصة متسلسلة وواضحة، وليس ككتلة فوضوية ضخمة.
جربوها في مشروعكم الجاي، وصدقوني راح تدعولي. قد تكون البداية صعبة قليلاً، ولكن الفوائد على المدى الطويل هائلة. يلا، همتكم يا شباب! 💪