الوحدة 5 · كتابة برامج حقيقية
الأخطاء وتصحيحها باستخدام DevTools
17 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
تقرأ رسائل الأخطاء وآثار المكدّس (stack traces)، وترمي الأخطاء وتلتقطها عن قصد بأنواع أخطاء مخصّصة، وتجد الأخطاء البرمجية بشكل منهجي باستخدام نقاط التوقّف ومصحّح الأخطاء في DevTools.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
كل مبرمج يكتب أخطاء برمجية كل يوم. وما يميّز المطوّرين ذوي الخبرة عن المبتدئين ليس قلّة الأخطاء، بل طريقة أهدأ وأسرع في العثور عليها. وهذا يبدأ بقراءة ما يقوله لك الحاسوب.
قراءة الخطأ
حين تعجز JavaScript عن الاستمرار، فإنها ترمي (throw) خطأً، وإن لم يلتقطه شيء، يعرض الـ console شيئًا كهذا:
Uncaught TypeError: Cannot read properties of undefined (reading 'amount')
at totalCents (expenses.js:12:31)
at renderSummary (ui.js:40:17)
at HTMLFormElement.<anonymous> (main.js:27:5)اقرأه في ثلاثة أجزاء. النوع، TypeError، يقول ما صنف المشكلة: قيمة كان نوعها خاطئًا بالنسبة لما فعلته بها. والرسالة تقول ماذا بالضبط: شيء ما قبل .amount كان undefined. وأثر المكدّس (stack trace) يسرد سلسلة استدعاءات الدوال التي أوصلت إلى هناك، الأحدث أولًا: فشلت totalCents في السطر 12 من expenses.js، وكانت قد استدعتها renderSummary، التي استدعاها معالج الإرسال في النموذج. وفي DevTools، كل موقع منها رابط يأخذك مباشرة إلى ذلك السطر.
الأنواع التي ستصادفها أكثر من غيرها:
| النوع | يعني عادةً |
|---|---|
ReferenceError |
اسم غير موجود: خطأ إملائي، أو استخدام قبل التصريح |
TypeError |
نوع خاطئ من القيم: استدعاء شيء ليس دالة، أو القراءة من undefined |
SyntaxError |
تعذّر تحليل الكود: قوس أو علامة تنصيص أو فاصلة مفقودة |
RangeError |
رقم خارج المسموح، مثل toFixed(200) |
رمي الأخطاء عن قصد
يمكنك أن ترمي الأخطاء بنفسك، وينبغي أن تفعل حين يُعطى كودك شيئًا لا يستطيع التعامل معه بشكل معقول:
function parseAmount(text) {
const dollars = Number(text);
if (text.trim() === "" || !Number.isFinite(dollars) || dollars <= 0) {
throw new Error(`Invalid amount: "${text}"`);
}
return Math.round(dollars * 100);
}
try {
console.log(parseAmount("12.20"));
console.log(parseAmount("twelve"));
console.log("this line never runs");
} catch (error) {
console.log("Caught:", error.message);
} finally {
console.log("finally always runs");
}throw توقف الدالة فورًا وتصعد في مكدّس الاستدعاءات حتى تلتقطها كتلة try/catch ما. وإن لم تلتقطها أي كتلة، تصبح خطأً غير ملتقط في الـ console. أما finally فتُنفَّذ سواء رُمي شيء أم لا، وهي المكان المناسب للتنظيف، مثل إخفاء مؤشّر التحميل.
أنواع أخطاء مخصّصة
بعض الأخطاء متوقّعة، مثل أن يكتب المستخدم مبلغًا غير صالح؛ وبعضها أخطاء برمجية. ويحتاج المُستدعون إلى التمييز بينها، وصنف الخطأ المخصّص يجعل ذلك ممكنًا:
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
function addFromForm(label, amountText) {
if (label.trim() === "") {
throw new ValidationError("label", "Enter what you spent money on.");
}
return { label: label.trim(), amount: Math.round(Number(amountText) * 100) };
}
try {
addFromForm(" ", "4.50");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Show next to ${error.field}: ${error.message}`);
} else {
throw error; // not ours to handle
}
}extends Error تجعل ValidationError نوعًا من Error له أثر مكدّس، وsuper(message) تنفّذ الـ constructor الخاص بالصنف الأب، والخاصية الإضافية field تخبر النموذج أين يعرض الرسالة. وكتلة catch لا تتعامل إلا مع ما تفهمه، وتعيد رمي كل ما عداه، فتظل الأخطاء البرمجية الحقيقية تطفو إلى السطح.
مصحّح الأخطاء: توقّف وانظر
console.log أداة أولى جيدة، لكنها لا تُريك إلا ما خطر لك أن تطبعه. أما مصحّح الأخطاء (debugger) في DevTools فيوقف برنامجك مؤقتًا عند سطر ما ويُريك كل شيء.
في Chrome أو Edge، افتح DevTools، واذهب إلى لوحة Sources، وافتح ملفك، وانقر على رقم سطر. هذا يضع نقطة توقّف (breakpoint). وفي المرة التالية التي يوشك فيها هذا السطر أن يُنفَّذ، يتوقّف كل شيء مؤقتًا، وتستطيع:
- أن تمرّر المؤشّر فوق أي متغيّر لترى قيمته، أو تقرأها كلها في قسم Scope؛
- أن تقرأ قسم Call Stack لترى كيف وصلت إلى هنا؛
- أن تتخطّى (step over: نفّذ هذا السطر وتوقّف عند التالي)، أو تدخل (step into: اتبع استدعاء الدالة إلى داخلها)، أو تخرج (step out: أكمل هذه الدالة)؛
- أن تكتب أي تعبير في الـ Console أثناء التوقّف، مستخدمًا القيم المتوقّفة.
انقر بالزر الأيمن على رقم سطر لتضيف نقطة توقّف مشروطة، مثل expense.amount > 100000، لا تتوقّف إلا حين يكون الشرط صحيحًا. ويمكنك أيضًا أن تكتب التعليمة debugger; في كودك: فحين تكون DevTools مفتوحة، يتوقّف التنفيذ هناك. احذفها قبل أن تُجري commit.
طريقة تصلح لكل خطأ
- أعِد إنتاجه بشكل موثوق. “أحيانًا يكون المجموع خاطئًا” تصبح “بعد حذف المصروف الأول، يكون المجموع خاطئًا”.
- اقرأ الخطأ، أو صِف بدقة ما توقّعته وما حصلت عليه.
- افترض: ما الشيء الواحد الذي يفسّر ذلك؟
- افحص القيم عند النقطة التي تشتبه بها، بنقطة توقّف أو بالطباعة، وجد أول مكان يختلف فيه الواقع عن توقّعك.
- أصلح السبب هناك، ثم تحقّق بالخطوات نفسها بالضبط من الخطوة 1.
والإغراء، خاصة حين تكون متعبًا، هو القفز مباشرة إلى تغيير الكود حتى يختفي العَرَض. وهذا يُنتج عادةً خطأً ثانيًا. عشر دقائق من النظر في القيم الحقيقية خير من ساعة من التخمين.
صار لديك الآن كل قطعة يحتاجها Pocket. وفي الدرس التالي تجمعها في التطبيق المكتمل.
أهم النقاط
- رسالة الخطأ تسمّي نوع الخطأ، وتقول ما الذي حدث، وأثر المكدّس (stack trace) الخاص بها يشير إلى الملف والسطر حيث وقع.
- ارمِ
Errorحين يعجز كودك عن الاستمرار بشكل معقول؛ والتقطه فقط حيث تستطيع فعل شيء مفيد، وأعِد رمي كل ما لم تتوقّعه. - صنف خطأ مخصّص (
class ValidationError extends Error) يتيح للمُستدعين التمييز بين المشاكل المتوقّعة والأخطاء البرمجية الحقيقية باستخدامinstanceof. - نقطة التوقّف (breakpoint) توقف كودك مؤقتًا عند سطر ما كي تفحص كل متغيّر وتتقدّم فيه سطرًا سطرًا؛ و
debugger;تضع واحدة من داخل الكود. - صحّح الأخطاء بالأدلة لا بالتخمين؛ أعِد إنتاج الخطأ، وجد أول سطر يختلف فيه الواقع عن توقّعك، ثم أصلحه.