يتناول هذا الفيديو نوعين من الأدوات نستخدمهما لفحص الكود. الأدوات الصورية (formal) ضيّقة ودقيقة: فهي تطبّق قواعد ثابتة على شروط محدّدة. ومنها الاختبارات وأدوات فحص الأنواع والـ linters. أما الأداة الضبابية (fuzzy) فهي وكيل البرمجة نفسه حين نستخدمه مراجِعًا. يستطيع تناول أسئلة واسعة، لكن حكمه قد يختلف حتى على الكود نفسه. سنرى كيف تتيح الفحوصات للوكيل أن يكتب الكود ويشغّله، ويقرأ رسالة الفشل ثم يحاول من جديد، ومتى يستحق إعداد هذه الحلقة العناء.
طُوِّرت الاختبارات وفحص الأنواع والـ linters لمساعدة الناس في البرمجة. فالناس يخطئون، وينسون تغييرات سابقة، ولا يحيطون بقاعدة كود كاملة في أذهانهم. إن لم تستخدموا linter من قبل، فالـ linter برنامج يقرأ الكود دون أن يشغّله، ويبلّغ عن أنماط تسمح بها اللغة لكنها مريبة، مثل عبارة import لا تُستخدَم أبدًا.
والوكلاء أيضًا يخطئون، ويغفلون عن تغييرات سابقة. وحتى حين يكون الوكيل بارعًا جدًا في مهمة ما، نحتاج إلى طريقة لاكتشاف الأخطاء التي يرتكبها.
فالفحوصات التي تكشف الأخطاء تساعد الناس والوكلاء على حدّ سواء، لأن كليهما يرتكب أخطاء يستطيع الفحص اكتشافها.
لننظر الآن إلى ما تنتهي به جلسة عادية. يكتب الوكيل بعض الكود ويخبركم أنه انتهى. لكن ما زال علينا أن نتحقّق من أن التغيير حقّق ما طلبناه.
الخيار الأول: أنتم. تشغّلون البرنامج، أو تقرؤون الـ diff، وتحدّدون هل حقّق التغيير ما طلبتموه. وهذا يستهلك انتباهكم مع كل تغيير.
ثم تقولون ما الخطأ، فيحاول الوكيل من جديد. هذه هي الحلقة التي يعمل بها معظم الناس في يومهم الأول مع وكيل.
كل دورة في هذه الحلقة تنتظر إنسانًا. إنها تسير بسرعة البشر، وتتوقف بمجرد أن تصرفوا نظركم عنها.
وهذه هي الحلقة التي نفضّلها. تبدأ بالطريقة نفسها: الوكيل يكتب بعض الكود.
ثم يشغّل فحصًا، مثل مجموعة الاختبارات أو أداة فحص الأنواع أو الـ linter. يجب أن يعطي الفحص نتيجة يستطيع الوكيل استخدامها دون أن ينتظركم حتى تفحصوا البرنامج.
إذا فشل الفحص، يقرأ الوكيل رسالة الفشل. رسالة الفشل الجيدة تذكر أي حالة اختبار فشلت وما كان متوقَّعًا، وهي أدقّ بكثير من قول أحدهم إن البرنامج لا يعمل.
يعدّل الوكيل الكود ويشغّل الفحص مرة أخرى. أحيانًا يكون الفحص خاطئًا، لذا قارنوا أي فشل غير متوقَّع بالمتطلَّب قبل تغيير الكود أو الاختبار. إذا تكرّر فشل الفحص ذاته، أو كان المتطلَّب نفسه غير واضح، فعلى الوكيل أن يتوقف ويسألكم بدلًا من مواصلة المحاولة.
عندما ينجح الفحص، يستطيع الوكيل أن يقدّم لكم التغيير ومعه تلك النتيجة. يستطيع أن يكرّر الفحص والإصلاح بين مراجعاتكم، ثم تراجعون أنتم هل يفي العمل بالمهمة. الفحص لا يجعل النموذج أذكى. بل يخبر الوكيل بما فشل والتغيير ما زال حديثًا، بدلًا من ترك الخلل لجلسة تصحيح أخطاء لاحقة.
تحتاج حلقة الفحص إلى تغذية راجعة يعتمد عليها الوكيل حين يقرّر ما يصلحه. الفحص الحتمي (deterministic) يعطي الإجابة نفسها للمُدخَل نفسه وتحت الظروف نفسها. وتشمل هذه الظروف كل ما يعتمد عليه الفحص إلى جانب الكود، مثل الساعة، أو الأرقام العشوائية، أو خدمة خارجية. فالاختبار الذي يقرأ الوقت الحالي أو يتصل بخادم حقيقي قد تتغيّر إجابته بينما يبقى الكود كما هو.
الاختبارات وأدوات فحص الأنواع والـ linters والمُصرِّفات تعمل كلها بهذه الطريقة. نسمّيها الأدوات الصورية. وكلمة «صورية» (formal) هنا لا تعني برهانًا رياضيًا. بل تعني أن برنامجًا يطبّق قواعد ثابتة، وتنتج عن ذلك خاصيتان. فهي ضيّقة: لا تفحص إلا الشروط التي أُعطيت لها. وهي دقيقة: كل شرط إما ينجح أو يفشل، دون أي تقدير شخصي بينهما.
تطبّق الأداة الصورية قواعدها مهما كانت ثقة الوكيل في وصف عمله. وهذا يمنح الحلقة نتيجة ثابتة تبني عليها.
الأداة الضبابية التي أشرنا إليها سابقًا هي وكيل البرمجة نفسه، أو النموذج اللغوي الذي يقف خلفه، حين نطلب منه مراجعة الكود. يستطيع الإجابة عن أي سؤال تقريبًا تطرحونه عن الكود، وهذا ما يجعله واسع النطاق.
تذكّروا أن النموذج اللغوي احتمالي (stochastic). فهو يولّد مُخرَجاته بسحب العيّنات، لذا فطرح السؤال نفسه عن الكود نفسه عدة مرات قد يعطي إجابات مختلفة. هنا لم يتغيّر الكود إطلاقًا. في تشغيل يقول إنه صحيح، وفي آخر يجد حالة غير مغطّاة، وفي ثالث يقترح اسمًا جديدًا فقط.
كما أن الوكيل قد لا يقرأ كل شيء ولا يفحص كل شيء. قد يتجاوز ملفًا مهمًا، أو يقول إنه فحص حالة دون أن يشغّل شيئًا.
لذا فحكم الوكيل قد يتغيّر بينما يبقى الكود كما هو. وهذا يجعله مفيدًا في المراجعة، لكنه ليس فحصًا ثابتًا للحلقة.
يجيب هذان النوعان من الأدوات عن أسئلة مختلفة. على اليسار شروط حوّلناها إلى فحوصات: السلوك المتوقَّع، وتوافق الأنواع، وقواعد المشروع. وعلى اليمين أسئلة مراجعة أوسع: هل الأسماء واضحة، وهل البنية منطقية، أو هل تنقص حالة مهمة.
الأداة الصورية تفحص بدقة الشروط التي أعطيتموها لها، ولا تغطي شيئًا غيرها. مجموعة اختبارات تفحص المُخرَجات المتوقَّعة لا تخبركم هل التصميم مربك.
أما وكيل البرمجة، حين نستخدمه أداةً ضبابية، فيشمل نوعي الأسئلة كليهما، ولهذا نريده. لكن إجابته قد تكون خاطئة حتى فيما كان يمكن التحقّق منه بفحص ثابت. اتساعه مفيد حين يقترح مشكلة لم يخطر لكم أن تختبروها، مثل القائمة الفارغة التي وجدتها إحدى المراجعات. وحين تقرّرون ما يجب أن تعطيه القائمة الفارغة، تصبح هذه النتيجة المتوقَّعة اختبارًا.
حين تستطيعون صياغة شرط بدقة، افحصوه بأداة حتمية بدلًا من سؤال الوكيل. فالفحص يعطي الإجابة نفسها في كل تشغيل. وهو أرخص أيضًا. تشغيل مجموعة اختبارات محلية يستغرق ثواني من وقت الحاسوب، بينما تُدفَع تكلفة مراجعة يجريها النموذج بالـ tokens المولَّدة، وهذا أغلى بكثير. لذا شغّلوا الفحوصات الحتمية أولًا، ثم اسألوا الوكيل عمّا لا تحسمه، مثل التسمية والبنية والحالات غير المغطّاة.
مراجعة أي تغيير تتطلّب انتباهكم في كل مرة. ما تعلّمتموه يساعدكم في المراجعة التالية، لكن عليكم مع ذلك أن تفحصوا العمل الجديد.
أما الفحص الآلي فيمكن إعادة استخدامه. فبعد كتابته، يمكن تشغيله بعد كل تغيير، وغالبًا في ثوانٍ قليلة. وعليكم صيانته حين يتغيّر المتطلَّب أو الكود المحيط به.
الفحص القابل لإعادة الاستخدام يقلّل ما عليكم التحقّق منه يدويًا بعد كل تغيير. تقرّرون مرة واحدة ما المهم، ثم يواصل الفحص اختباره.
للاختبارات ثمن. فطلب كتابتها من الوكيل يعني عملًا إضافيًا له، يستهلك tokens أكثر ووقتًا أطول في كل مهمة.
وهي أيضًا كود إضافي يحتاج إلى صيانة. حين يتغيّر متطلَّب ما، يجب أن تتغيّر معه الاختبارات التي تصفه.
هذه التكاليف سهلة الملاحظة. أما العائد فيأتي من جهتين. قد يجد الاختبار خللًا في التغيير الذي تجرونه الآن. ولأن الاختبارات تتراكم، يظلّ الاختبار يفحص ذلك السلوك مع كل تغيير لاحق.
لنأخذ تطبيقًا صغيرًا لقائمة مهام تُضاف إليه ثلاث ميزات: إضافة عناصر، وإتمامها، وحفظ القائمة وتحميلها. بعد الميزات الثلاث، يفحصها كلَّها تشغيلٌ واحد للاختبارات، لأن اختبارات الميزات السابقة تبقى في المستودع.
قد يُعطّل تغييرٌ ميزةً كانت تعمل من قبل. يُسمّى هذا الفشل تراجعًا (regression). هنا، عطّلت إضافةُ الحفظ والتحميل إضافةَ العناصر، واختبارات الإضافة تكتشف ذلك. فلا داعي لاختبار الميزات السابقة يدويًا من جديد. ونجاح تلك الاختبارات يمنحكم بعض الثقة بأن ما كان يعمل من قبل ما زال يعمل. وكذلك الخطأ البرمجي الذي تصلحونه. احتفظوا بالاختبار الذي يُعيد إنتاج الخطأ، فإن عاد الخطأ، فشل الاختبار مجددًا.
الاختبارات القديمة لا ترى كل شيء. قد تتفاعل ميزة جديدة مع ميزة سابقة بطرق لم تجرّبها تلك الاختبارات قط. إتمام عنصر يعمل، وحفظ القائمة وتحميلها يعمل. والآن أتمّوا عنصرًا، واحفظوا القائمة، ثم حمّلوها بعد إعادة تشغيل التطبيق. اختاروا عامل التصفية Active، فيظهر العنصر المكتمل بين العناصر النشطة، لأن البيانات المحفوظة لم تسجّل هل اكتمل كل عنصر. لم يُجرِ أي اختبار موجود هذه الخطوات معًا، لذا تحتاج هذه التركيبة إلى اختبار خاص بها.
الاختبارات القديمة تكتشف التراجعات في السلوك الذي تفحصه. لكنها لا تفحص تركيبات لا تجرّبها أبدًا، فحين تتشارك الميزات حالة البرنامج (state)، اختبروها معًا وأبقوا الاختبار.
مجموعة الفحوصات تمنح أكثر من الحماية من التراجعات. أولًا، تستطيع جلسة جديدة تشغيل مجموعة الفحوصات لفحص متطلبات وُضعت في جلسات سابقة. فهذه الفحوصات تبقى في المستودع حتى بعد أن تختفي المحادثة.
ثانيًا، الفحوصات القابلة للتكرار تقلّل مقدار السلوك الذي عليكم التحقّق منه يدويًا عند مراجعة تغيير.
هناك حالة تصبح فيها الفحوصات مهمة بشكل خاص. الوكيل مستعد لكتابة برنامج بلغة لم تستخدموها قط، لكنكم تفقدون القدرة على مراجعته بطلاقة. ما زال بإمكانكم تحديد السلوك المتوقَّع وتشغيل الاختبارات ريثما تتعلّمون ما يكفي لقراءة الكود، أو تستعينون بمن يراجعه.
لا يعني أيّ من هذا أن كل مشروع يجب أن تكون له اختبارات. فكّروا في الأمر كطيف من تكرار الاستخدام. في أحد طرفيه، برنامج تجريبي تحذفونه يوم الخميس. وفي الطرف الآخر، مستودع ينفّذ إليه ثلاثة أشخاص push كل يوم.
للفحوصات تكلفة: الإعداد والـ tokens ووقت التشغيل والصيانة. وفي المقابل تمنح الموثوقية، وتتيح للوكيل إنجاز عمل أكثر دون إشراف. البرنامج التجريبي قصير العمر يعيد تشغيل فحوصاته بضع مرات فقط قبل أن يُحذف. أما في مستودع مشترك، فكل تغيير يعيد تشغيلها.
اسألوا: هل لهذا الكود مستقبل؟ هل ستواصلون أنتم أو غيركم تعديله؟ كلما زاد عدد مرات تشغيل فحص مفيد، زاد المبرّر لبنائه. وفكّروا أيضًا في تكلفة الخطأ في هذه المهمة بالذات. سكربت لمرة واحدة يحوّل بيانات مهمة قد لا يُشغَّل إلا مرة، لكن الخطأ فيه مكلف، لذا فهو يستحق فحصًا دقيقًا رغم ذلك.
الأدوات الصورية ضيّقة ودقيقة في الشروط المحدّدة. والأدوات الضبابية واسعة النطاق، لكن أحكامها قد تختلف. ومعًا تغطيان الفحوصات التي نستطيع تحديدها، والأسئلة الأوسع التي ما زلنا بحاجة إلى مراجعتها.
الفحوصات التي تحتفظون بها تواصل اختبار المتطلبات السابقة بعد كل تغيير، فلا يحتاج أحد إلى إعادة فحصها يدويًا. وحدودها هي ما لا تجرّبه أبدًا. حين تتشارك الميزات حالة البرنامج، تحتاج تركيبتها إلى اختبار خاص بها.
هناك ثلاثة أنواع شائعة من الفحوصات الصورية. الاختبارات تقارن السلوك بما هو متوقَّع. فحص الأنواع يتحقّق من أن القيم تناسب مواضع استخدامها. والـ linters تفحص أنماطًا مختارة وقواعد المشروع.
في البداية، قال الوكيل إنه انتهى، وكان عليكم فحص كل تغيير بأنفسكم. ومع وجود الفحوصات، يستطيع الوكيل تشغيلها، وقراءة ما فشل، وإصلاحه، ثم يقدّم لكم تغييرًا يجتازها بالفعل. وعندها تركّز مراجعتكم على ما لا تغطيه الفحوصات.
نتمنى لكم اختبارات موفّقة. نراكم في الفيديو التالي.