لماذا يصعب عزو أخطاء الشيفرة التي يولّدها GitHub Copilot إلى مصدرها
للإكمال المضمّن داخل المحرر ملمح مخاطر يختلف عن وكيل يعيد كتابة الملفات. فكل اقتراح صغير بما يكفي ليبدو قابلاً للمراجعة، فتراجعه في ثانية وتمضي. وتلك الثانية من الانتباه صادقة تجاه السطر الماثل أمامك، وعمياء تجاه النظام المحيط به.
والنتيجة أنه حين ينكسر شيء ما، يصبح تتبّع السبب بالتنصيف عملاً مضنياً. فلا يوجد commit واحد مشبوه، بل ذيل طويل من الإكمالات المقبولة التي بدا كل منها صحيحاً في حينه.
ثلاثة أنواع من الانحراف تستحق الانتباه
انحراف الاصطلاحات
تتبع الإكمالات أنماطاً مستمدة من تدريبها ومن الشيفرة المجاورة، وهي ليست دائماً الاصطلاح الفعلي المعتمد في قاعدة شيفرتك.
فمعالجة الأخطاء وفحوص القيم الفارغة والتسجيل تتباعد ببطء بين وحدة وأخرى.
المنطق المكرّر
قبول دالة مساعدة مولَّدة أسرع من البحث عن الدالة الموجودة أصلاً، فينتهي الأمر بتطبيق القاعدة نفسها في ثلاثة مواضع.
ثم يُصلَح أحدها لاحقاً ويبقى الاثنان الآخران على حالهما.
القيم الافتراضية المعقولة ظاهرياً
قيمة افتراضية مقترحة أو مهلة زمنية أو ترتيب فرز يبدو معقولاً ولا يطابق القاعدة المعتمدة في منتجك.
لا شيء يفشل. السلوك ببساطة ليس السلوك الذي قصده أحد.
والأنواع الثلاثة جميعها غير مرئية أثناء المراجعة ومرئية عند تشغيل المنتج، وهذا ما يحدد المكان الذي ينتمي إليه التحقق.
تحقّق من السلوك على إيقاع منتظم، لا عند كل إكمال
التحقق من كل اقتراح مقبول ليس ممكناً ولا مفيداً. الوحدة الصحيحة هي طلب السحب (pull request): فعنده يكون قد تجمّع قدر متماسك من التغيير، وهو ما يزال صغيراً بما يكفي لتحليله إن وقع خلل.
وما تريد التحقق منه ليس الشيفرة الجديدة، بل السلوك الذي تعمل تلك الشيفرة داخله. فطلب السحب الذي مسّ وحدة الفواتير ينبغي أن يختبر الفوترة من طرفها إلى طرفها، بما في ذلك المسارات التي لم تخطر ببال كاتبه، لأنها بالضبط حيث تختبئ قيمة افتراضية معقولة ظاهرياً.
وتثبيت مهارة التحقق يتيح للوكيل أن يقوم بذلك بنفسه بدل انتظار أن يتذكره إنسان.
الطرفية
npm install -g @testsprite/testsprite-cli
testsprite setup
وتغطي لوحة التحكم المجال نفسه لمن لا يرغب في تثبيت محلي، أما مجموعة الأوامر الكاملة فتوجد في مستودع CLI.
سؤال الانحدار الذي لا يطرحه أحد في وقت مبكر بما يكفي
السؤال المفيد عن قاعدة شيفرة تعتمد بكثافة على الإكمال ليس «هل هذه الشيفرة جيدة»، بل «هل ما يزال المنتج يفعل ما كان يفعله الشهر الماضي». وهما سؤالان مختلفان، والثاني وحده هو الذي يلتقط الانحراف.
والإجابة عنه تتطلب مجموعة فحوص سابقة للتغيير، وهذا هو الجزء الذي تؤجله الفرق. فعشرة مسارات مدوَّنة في الأسبوع الأول أثمن من مئة مسار تُدوَّن بعد أول حادثة، لأن العشرة الأولى وحدها هي التي حُدِّدت قبل أن يعرف أحد أيها سيهم.
عادة المراجعة القابلة للتوسّع
مراجعة كل إكمال ليست ممكنة، وعدم مراجعة أي منها هو ما يجعل الانحراف يتراكم. والعادة التي تنجح هي المراجعة حسب الفئة لا حسب السطر.
حين يُدخل إكمالٌ مسار خطأ، تحقق من مطابقته لطريقة تعامل بقية الوحدة مع الأخطاء، لأن التباعد هنا هو أشيع أنواع الانحراف وأكثرها إزعاجاً عند تفكيكه لاحقاً. وحين يُدخل قيمة افتراضية، اسأل من أين جاءت تلك القيمة، لأن القيمة الافتراضية المعقولة ظاهرياً هي أهدأ وسيلة لتغيير السلوك. وحين يُدخل دالة مساعدة، امنح عشر ثوانٍ للبحث عن الدالة الموجودة أصلاً، إذ إن المنطق المكرّر هو ما يجعل أي إصلاح لاحق ناقصاً.
كل فحص من هذه الفحوص الثلاثة يستغرق ثوانٍ معدودة ويلتقط معظم ما يتراكم. وما عدا ذلك فالتقاطه بتشغيل المنتج أجدى من التقاطه بقراءته.
اجعله تلقائياً
الانحراف تدريجي، لذا يجب أن يكون التحقق منتظماً إلى حد الرتابة. فأي إجراء يتوقف على تذكّر أحدهم سيُتخطى تحديداً في الأسبوع المزدحم الذي يُقبل فيه أكبر عدد من الإكمالات.
وإذا كان خط النشر يخص فريقاً آخر، فإن GitHub App هو الطريق الأقل مقاومة: فهو مجرد webhook لا يغيّر شيئاً في مستودعك، وينطلق عندما تُعلن عملية البناء لديك أن النسخة الجديدة أصبحت مباشرة. وإن أردت بدلاً من ذلك أن يكون التحقق ظاهراً داخل المستودع، فإن خطوة في GitHub Actions تؤدي هذا الغرض.
التقاط الانحراف دون قراءة كل إكمال
مراجعة كل اقتراح مقبول ليست ممكنة، لذا تتحقق TestSprite بدلاً من ذلك من السلوك الذي تعمل الشيفرة داخله. تُولَّد التغطية من منتجك، وتُنفَّذ مع كل طلب سحب، وتجيب عن السؤال الذي يهم قاعدة شيفرة تعتمد بكثافة على الإكمال: هل ما يزال هذا يفعل ما كان يفعله الشهر الماضي.
وهذا يلتقط أنواع الانحراف الثلاثة مباشرة. فالقيمة الافتراضية المعقولة ظاهرياً التي غيّرت ترتيب الفرز تظهر على هيئة مسار يتصرف تصرفاً مختلفاً. والمنطق المكرّر يظهر حين تُصلَح إحدى النسختين ولا تُصلَح الأخرى. وانحراف الاصطلاحات في معالجة الأخطاء يظهر على هيئة مسار صار يفشل في صمت.
وهو يعمل حيث يقع التغيير، في صورة تعليق على طلب السحب أو فحص على الـ commit، فيشير أي انحدار إلى دفعة واحدة من الإكمالات لا إلى ربع سنة منها.
هل الشيفرة التي يولّدها Copilot أسوأ من الشيفرة المكتوبة يدوياً؟
ليست كذلك على مستوى السطر الواحد في معظم الحالات. فالخطر متعلق بالحجم: إذ تدخل إلى المستودع في الساعة الواحدة شيفرة أكثر مما صُمّمت عملية المراجعة لاستيعابه، فينتج عن معدل العيوب نفسه عدد أكبر من العيوب المفلتة.
هل تحلّ مراجعة أكثر صرامة هذه المشكلة؟
جزئياً، وهي تتعارض مع السبب الذي يدفع الناس إلى استخدام الإكمال أصلاً. أما نقل التحقق من القراءة إلى التحقق السلوكي فيحافظ على السرعة ويعيد شبكة الأمان.
وماذا عن الاختبارات التي يكتبها Copilot؟
مفيدة للتغطية، ضعيفة كفحص مستقل. فالاختبار المولَّد من الافتراضات نفسها التي وُلِّدت منها الشيفرة يتفق معها بحكم تكوينه.
كم عدد المسارات التي ينبغي أن نغطيها أولاً؟
ابدأ بالمسارات القليلة التي كنت ستعرضها على عميل، إضافة إلى أي مسار يمسّ المال أو الصلاحيات. ثم وسّع انطلاقاً من الحوادث لا من هدف للتغطية.
هل يحتاج هذا إلى صلاحية الوصول إلى المستودع؟
يجري التحقق على التطبيق المنشور عبر واجهته. ولا يُستخدَم الوصول إلى المستودع إلا لنشر النتائج على طلبات السحب والـ commits.
العيب هو الانحراف، لا الاقتراح.
تتراكم أخطاء الشيفرة التي يولّدها GitHub Copilot عبر كثير من الإكمالات الصغيرة المقبولة. تحقّق من السلوك عند كل طلب سحب بدل كل اقتراح، وحدّد المسارات قبل أن تحتاج إليها، وأبقِ التحقق يعمل تلقائياً.