للجميع ردّ الفعل الأول نفسه، وهو لا يتحسّن كثيرًا مع الخبرة: تزيغ العينان، وتهبط المعدة، وتشتدّ الرغبة في إغلاق النافذة. عشرون سطرًا باللون الأحمر، نصفها يسمّي ملفات لم تسمع بها قط، وينتهي كله بشيء مثل TypeError: Cannot read properties of undefined (reading 'map').
ويستحقّ الأمر أن يُقال صراحةً، لأنه سبب وجود هذه الصفحة: كل من يستسلم في العمل مع الكود تقريبًا يستسلم أثناء خطأ. الجدار الأحمر يُقرَأ كحكم — على المشروع، وقليلًا عليك أنت. وهو ليس كذلك. إنه آلة تكون حرفيّة إلى أقصى حدّ بشأن النقطة الدقيقة التي علقت عندها، وهذه الحرفيّة تحديدًا هي ما يجعل المشكلة قابلة للحلّ.
ما الموجود هناك فعلًا
الـ stack trace هو قائمة استدعاءات الـ functions التي كانت جارية لحظة فشل شيء ما. يبدو عدائيًا لأنه صيغ لآلة — مسارات ملفات كاملة، وأرقام أسطر، وأسماء functions، وأحيانًا أعداد — لكن انزع ذلك عنه ولن يخبرك سوى ثلاثة أشياء:
- رسالة الخطأ — ما الذي حدث، في سطر واحد.
- ملف ورقم سطر — أين حدث.
- سلسلة الاستدعاءات التي أوصلت إليه — كيف وصل البرنامج إلى ذلك الموضع.
هذه هي المفردات كلها. كل trace ستراه في حياتك، بأيّ لغة، هو هذه الأشياء الثلاثة مرتديةً علامات ترقيم مختلفة.
وثمّة أمر صغير يستحقّ أن تعرفه، لأنه الموضع الذي تخطئ فيه النصائح الواثقة عادةً: ترتيب تلك السلسلة ليس ثابتًا بين اللغات. Python تطبع أعمق استدعاء أخيرًا، فأسفل الـ traceback في Python هو موضع العطب. أما JavaScript وNode فتطبعان أعمق استدعاء أولًا، فالأعلى هو الموضع. ولـ Java وRuby وGo وغيرها كلٌّ عرفها الخاص.
ولهذا بالضبط تأتي النصيحة أدناه على ما هي عليه. لا يمكنك أن تعرف بثقة أيّ طرف من الـ trace هو المهمّ دون أن تعرف مسبقًا عُرف اللغة — فلا تحاول أن تختار.
الحركة
تصادف خطأ. تنسخ الشيء كاملًا — كل سطر، بما في ذلك الأسطر الفارغة والأجزاء التي تبدو ضوضاء — وتلصقه في المحادثة مع جملة عمّا كنت تفعله. الصياغة لا تهمّ حقًا:
تظهر لي رسالة الخطأ هذه. ماذا تعني وأين أبحث؟
[الصق الـ trace كاملًا هنا]
I'm getting this error. What does it mean and where do I look?
[paste the full trace here]
ما يعود إليك هو الرسالة بلغة بسيطة، وإشارة إلى الملف والسطر اللذين سبّباها فعلًا، ونظرة في ذلك الملف عادةً، ثم إمّا إصلاح وإمّا الشيء التالي الذي تفحصه. النصّ المخيف يصير فقرة.
لا تقتطعه
هذا هو الخطأ الوحيد الذي يستحقّ أن يُسمّى، لأنه شبه شامل ولأنه يبدو نقيض الخطأ. يلصق الناس السطر الأخير وحده — أو السطر الذي يبدو ذا معنى — بحجّة أنهم يزيلون الضوضاء ويدخلون في صلب الموضوع.
ما يزيلونه في الحقيقة هو سلسلة السببية. السطر الذي يظهر فيه الخطأ ليس في الغالب السطر الذي نشأ فيه: شيء ما سُلّم قيمة سيئة قبل أربعة استدعاءات ولم يشتكِ أحد حتى استُخدمت. اقطع السلسلة وتكون قد حذفت الجزء الذي يفسّر كيف انتهى سطر بريء المظهر تمامًا إلى الفشل.
الصق الشيء كاملًا. لا تُدلِ برأيك، ولا تنتقِ مسبقًا السطر الذي تظنّه المهمّ — اختيار الجزء ذي الصلة هو العمل التشخيصي نفسه، وهو الجزء الذي تحاول أن تحصل على مساعدة فيه.
وإن كان المُخرَج قد مرّ خارج الشاشة، مرّر إلى الأعلى وخذ الكتلة من أول سطر فارغ فوقه حتى آخر سطر أحمر.
أخطاء لا تأتي مع trace
كثير منها كذلك. ستحصل على سطر واحد مقتضب في log — command not found، port 3000 already in use — أو اختبار فاشل يعرض المتوقَّع مقابل الفعلي، أو خطّ متعرّج مع تلميح، أو مجرّد مربّع أحمر في صفحة كان يُفترض أن يحدث فيها شيء.
تعامل معها كلها بالطريقة نفسها: انسخ النصّ، وأضف ما كنت تفعله حين ظهر.
طلبت منك أن تشغّل هذا المشروع فحصلت على هذا. ما الخطأ؟
[الصق المُخرَج]
I asked you to start this project and got this. What's wrong?
[paste the output]
ضع ذلك إلى جانب «لا يعمل» ويصير الفرق بديهيًا. وكل من حاول أن يساعد قريبًا له في مشكلة حاسوبه عبر الرسائل النصّية يفهم هذا فهمًا عميقًا وشخصيًا.
حين يشير إلى كود شخص آخر
هذه هي اللحظة التي تجعل الناس يستسلمون، وهي مبنيّة على سوء فهم يستحقّ التفكيك مباشرةً.
الـ trace يشير إلى مكان لم تطأه قط — داخليات framework، أو package لم تثبّته عن قصد، أو أحشاء runtime اللغة. والاستنتاج الذي يبدو معقولًا: أنا لا أعرف هذا الكود، فلا يمكن أن أصلح هذا.
أنت لست مضطرًّا إلى ذلك في الغالب الأعمّ. الخطأ الذي يُطلَق داخل كود شخص آخر يكون سببه دائمًا تقريبًا أن كودك أنت استخدمه بطريقة لم يتوقّعها.
بشكل ملموس: لنفترض أن ملفًا يقول require('./emial') بينما ينبغي أن يقول require('./email'). تشغيل المشروع ينفجر بجدار أحمر ينتهي بـ Error: Cannot find module './emial'، مُطلَقًا من عمق module loader في Node — ملف لم تفتحه قط ولا شأن لك بفتحه. الصق الجدار، فيكون الجواب:
«هذا يُطلَق من نظام
requireفي Node، لكن سببهserver/index.js:4— كتبتrequire('./emial')ولا وجود لملف بهذا الاسم. قصدت
خطأ إملائي واحد، في ملفك أنت، في سطر كتبته أنت. الـ library كانت تعمل تمامًا طوال الوقت. هذا هو شكل الغالبية الساحقة من الأخطاء المخيفة المظهر، واستيعاب ذلك هو معظم ما يفصل بين من يجد التصحيح محتمَلًا ومن لا يجده كذلك.
إعادة التأطير التي تستحقّ أن تبقى
الأخطاء ليست عقابًا، وليست إشارة إلى أنك تخوض فوق طاقتك. إنها أدقّ تغذية راجعة ستحصل عليها من حاسوب على الإطلاق — آلة تخبرك، بدقّة تامّة وبلا أيّ لباقة، بالنقطة المضبوطة التي كفّت عندها تعليماتها عن أن تكون ذات معنى.
وسبب كون ذلك عزاءً باردًا فيما مضى أن الدقّة كانت مشفّرة في صيغة كُتبت للآلة لا لك. هذا هو الجزء الذي تغيّر. الدقّة كانت نافعة دائمًا؛ صارت الآن مقروءة أيضًا.
وبعد عدد كافٍ من هذه، يكفّ الجدار الأحمر عن أن يُقرَأ كجدار ويبدأ أن يُقرَأ كباب. باب مهيب أحيانًا. لكنه باب.
ومتى كان الكود غير معطوب بل غير مألوف فحسب، تكون الحركة المرافقة هي التتبّع — تتبّع قيمة إلى الوراء نحو مصدرها، أو إلى الأمام نحو كل ما يعتمد عليها.