العودة إلى المدونة

البرمجة2 مارس 20264 دقائق قراءة

صحّح الأخطاء كعالِم، لا كمقامر

أسامـــة الدروشـــي

نظارة أمام شاشات مليئة بالشيفرة البرمجية

هناك طريقتان لمطاردة خطأ برمجي. الأولى: تغيّر شيئًا، تعيد التشغيل، تحدّق في المخرجات، ثم تغيّر شيئًا آخر. تشعر أنك تُنجز لأنك تتحرك باستمرار. والثانية: تصوغ ادعاءً دقيقًا عمّا هو معطوب، ثم تصمّم أرخص اختبار ممكن قادر على إثبات خطئك. الأولى مقامرة، والثانية علم. بعد ست سنوات في هذه المهنة جرّبت الطريقتين بما يكفي، ودار القمار تربح دائمًا في اللعبة الأولى.

الليلة التي «انهارت» فيها الأبراج

قبل مدة كنت أبني منصة تحليلات لرصد تدهور أداء أبراج الاتصالات لدى أحد المشغّلين. وظيفة اللوحة بسيطة الوصف: راقب مؤشرات كل برج، قارنها بخط أساس متحرك، وأطلق تنبيهًا عند التدهور. وذات صباح اشتعلت قناة فريق التشغيل: مئات التنبيهات، وكأن كل أبراج منطقة كاملة تدهورت دفعة واحدة، وكلها تحمل طوابع زمنية بُعيد منتصف الليل. إما أن نصف الشبكة سقط أثناء الليل، وإما أن منصتنا تكذب.

الشبكة كانت سليمة، والمنصة هي التي تكذب. والإغراء في التاسعة صباحًا، وأصحاب المصلحة يراقبون، أن تبدأ بالعبث: خفّف العتبات، وسّع نافذة خط الأساس، أضف مهلة تهدئة (debounce)، انشر النسخة وانتظر يومًا. كل خطوة من هذه رمية نرد، وبعضها كان «سينجح» فعلًا، لكن بدفن العَرَض لا بمعالجة السبب.

بدل ذلك ألزمنا أنفسنا بإعادة إنتاج المشكلة أولًا. سحبنا القياسات الخام لبرج واحد صاخب إلى قاعدة PostgreSQL محلية وأعدنا تشغيل مهمة التجميع عليها. عاد الطوفان، على نحوٍ حتمي، في كل مرة. هذه الخطوة وحدها غيّرت كل شيء: الخطأ القابل لإعادة الإنتاج حقيقة يمكنك استجوابها.

الخطأ الذي لا تستطيع إعادة إنتاجه ليس خطأً بعد؛ إنه إشاعة.

وما إن أصبحت إعادة الإنتاج بين أيدينا حتى برز النمط: التنبيهات لا تتجمع عند منتصف الليل بالتوقيت المحلي، بل عند 00:00 بتوقيت UTC، أي الرابعة فجرًا في مسقط. هذا ليس ضجيجًا؛ هذا توقيع.

فرضية واحدة، وأرخص تجربة ممكنة

كتبنا الادعاء في جملة واحدة: مهمة التجميع تُقسّم القياسات إلى أيام بتوقيت UTC، بينما يُحسب خط الأساس بالتوقيت المحلي، فتظل سلة «اليوم» شبه فارغة لأربع ساعات بعد 00:00 UTC، ويبدو كل برج وكأن مؤشراته هبطت بنسبة 90%.

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

كلّفت التجربة عشر دقائق: أمر SET TIME ZONE 'UTC' واحد في جلسة قاعدة البيانات التي نعيد التشغيل عليها، ثم تشغيل ومراقبة. اختفى الطوفان. الخطأ الفعلي كان تعبيرًا واحدًا: DATE(recorded_at) داخل SQL view يستخدم المنطقة الزمنية للجلسة بصمت، ويغذّي مهمة Python تُنجز كل شيء آخر بتوقيت UTC. مكوّنان، كلٌّ منهما متسق داخليًا، يختلفان حول لحظة بداية «اليوم».

كان الإصلاح مملًا، أما العثور عليه فلم يكن حظًا بل إجراءً:

  • أعد إنتاج المشكلة أولًا. على نحوٍ حتمي، وعلى أصغر شريحة بيانات لا تزال تُظهر السلوك.
  • صُغ فرضية واحدة قابلة للدحض. إن لم تستطع تحديد الدليل الذي يقتلها، فهي حدس لا فرضية.
  • نفّذ أرخص تجربة قادرة على دحضها. ليست الأكثر شمولًا؛ الأرخص.
  • قسّم فضاء الاحتمالات. كل تجربة ينبغي أن تُقصي نصف الاحتمالات المتبقية تقريبًا: أي مرحلة في خط المعالجة؟ أي نافذة زمنية؟ أي commit؟ وما git bisect إلا هذه الفكرة بواجهة جاهزة.

المقامرة تستكشف تكوينًا واحدًا في كل محاولة، أما التقسيم الثنائي فيقتل نصف فضاء البحث في كل محاولة. وعلى امتداد جلسة تصحيح طويلة، ليس هذا فرقًا في الأسلوب؛ إنه فرق أُسّي.

السجلات دفتر مختبرك

لا شيء من هذا ينجح إذا كان النظام عاجزًا عن أن يشهد على سلوكه. السبب الوحيد الذي جعلنا نلمح توقيع 00:00 UTC أصلًا هو أن تنبيهاتنا كانت تحمل طوابع زمنية كاملة مقرونة بمنطقتها الزمنية، لا أوقاتًا محلية مجردة من أي منطقة زمنية. كان ذلك انضباطًا دفع أحدهم ثمنه قبل شهور.

العادات التي لم أعد أساوم عليها: سجّل الطوابع الزمنية مع الإزاحة الزمنية دائمًا. سجّل المدخلات عند حدود النظام، فهناك تلتقي افتراضات المكوّنات، وهناك تتعارض. وسجّل القرارات لا الأحداث فقط: عبارة مثل «أُطلق التنبيه لأن خط الأساس = X والقيمة الحالية = Y والعتبة = Z» تحوّل اللغز إلى استعلام.

قراءة كود لم تكتبه

الـ view المعطوب كتبه شخص غادر الشركة، والغريزة الأولى في هذا الموقف هي الارتياب في الكود وإدانة كاتبه. قاوم الاثنين. اقرأه كما يقرأ العالِم دفاتر الملاحظات الميدانية: الكود هو الحقيقة الفعلية لما يفعله النظام، لا الوثائق، ولا المخطط في Confluence، ولا ذاكرتك عن اجتماع قديم. من كتب DATE(recorded_at) لم يكن مهملًا؛ لقد اختبره على خادم يعمل مصادفةً بالتوقيت المحلي فكان صحيحًا هناك. لم يكن الخطأ في السطر، بل عند خط التماس بين افتراضين معقولين.

هذه هي الحجة الحقيقية للتصحيح كعالِم. المقامر يحتاج إلى الحظ، والحظ لا ينتقل إلى غيرك. أما سجل الفرضيات فينتقل. بعد ستة أشهر، حين ظهرت تذكرة «صفوف مكررة قرب منتصف الليل» في خط معالجة مختلف تمامًا، كان أول سؤال يطرحه الجميع: منتصف ليل مَن؟