اذهب إلى المحتوى

تنقيح أخطاء Node.js باستخدام المنقح debugger وأدوات المطور DevTools


Hassan Hedr

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

وعادة ما يضيف المطورون تعليمات الطباعة داخل شيفرة البرنامج لمعاينة بعض القيم أثناء تشغيله، حيث يضيف المطور في نود تعليمات طباعة مثل ‎console.log()‎ و ‎console.debug()‎، ومع أن هذه الطريقة سهلة وسريعة لكنها تبقى يدوية ولا تخدم دومًا في الحالات المعقدة أو عندما يكون التطبيق كبيرًا، فقد ينسى أحيانًا المطور بعض تعليمات الطباعة تلك ما قد يؤدي لطباعة معلومات خاصة وحساسة عن التطبيق يجعله عرضة للاختراق، وهنا يوفر لنا المنقح طريقة أفضل لمراقبة البرنامج أثناء التشغيل دون أن يُعرّض البرنامج لمثل تلك الأخطار.

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

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

المستلزمات

هذا المقال جزء من سلسلة دليل تعلم Node.js لذا يجب قبل قراءته:

استخدام الراصدات Watchers مع المنقح Debugger

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

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

$ mkdir debugging

وندخل إلى المجلد:

$ cd debugging

ننشئ داخله ملف جافاسكربت جديد بالاسم ‎badLoop.js‎ ونفتحه ضمن أي محرر نصوص، حيث سنستخدم في أمثلتنا محرر نانو ‎nano‎ كالتالي:

$ nano badLoop.js

سنكتب برنامجًا يمر على عناصر المصفوفة ويجمع قيمها لحساب المجموع الكلي لها، حيث تمثل تلك الأرقام عدد الطلبات اليومي لمتجر خلال فترة أسبوع، حيث سيطبع البرنامج المجموع الكلي للأرقام في تلك المصفوفة، ليكون البرنامج كالتالي:

let orders = [341, 454, 198, 264, 307];

let totalOrders = 0;

for (let i = 0; i <= orders.length; i++) {
  totalOrders += orders[i];
}

console.log(totalOrders);

أنشأنا بداية مصفوفة الطلبات ‎orders‎ والتي تحوي خمسة أعداد، ثم أنشأنا متغير المجموع الكلي للطلبات ‎totalOrders‎ وضبطنا قيمته الأولية إلى الصفر ‎0‎، حيث سنخزن ضمنه المجموع الكلي للأرقام السابقة، ومررنا ضمن حلقة ‎for‎ على عناصر المصفوفة ‎orders‎ وأضفنا كل قيمة منها إلى متغير المجموع الكلي ‎totalOrders‎، ثم أخيرًا طبعنا قيمة المجموع الكلي.

والآن نحفظ الملف ونخرج منه وننفذ البرنامج ونعاين النتيجة:

$ node badLoop.js

يظهر لنا الخرج التالي:

NaN

القيمة ‎NaN‎ في جافاسكربت هي اختصار لجملة "ليس عددًا" أو "Not a Number"، ولكن كيف حصلنا على تلك القيمة مع أن المصفوفة لا تحوي سوى قيم عددية؟ الطريقة الأفضل لمعرفة سبب المشكلة هي استخدام منقح الأخطاء، وهنا سنبدأ بالتعرف على منقح نود ونستخدمه رصد قيمة كل من المتغيرين ‎totalOrders‎ و ‎i‎ ضمن حلقة ‎for‎، ولتشغيله نضيف خيار ‎inspect‎ قبل اسم الملف عند تشغيله بواسطة الأمر ‎node‎ كالتالي:

$ node inspect badLoop.js

سنلاحظ ظهور الخرج التالي:

< Debugger listening on ws://127.0.0.1:9229/e1ebba25-04b8-410b-811e-8a0c0902717a
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in badLoop.js:1
> 1 let orders = [341, 454, 198, 264, 307];
  2 
  3 let totalOrders = 0;

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

وبعد ربط منقح الأخطاء ستظهر الرسالة ‎Break on start in badLoop.js:1‎ والتي تعني توقف التنفيذ عند أول سطر من الملف، حيث يمكن وضع نقاط الوقوف ضمن الشيفرة لتحديد مكان توقف التنفيذ وكما لاحظنا فمنقح الأخطاء يتوقف افتراضيًا عند أول سطر من الملف دومًا ويُظهر لنا مقطع من الشيفرة عند مكان التوقف وبعده سطر جديد يبدأ بالكلمة ‎debug‎ يمكننا كتابة الأوامر ضمنه:

...
> 1 let orders = [341, 454, 198, 264, 307];
  2 
  3 let totalOrders = 0;
debug>

نلاحظ وجود الرمز ‎>‎ بجانب رقم السطر الأول ‎1‎ وهو دلالة على مكان توقف التنفيذ الحالي، ويُظهر السطر الأخير استعداد منقح الأخطاء لتلقي الأوامر، حيث يمكننا مثلًا تنفيذ أمر لتوجيهه لتقديم عملية التنفيذ خطوة إلى الأمام والذهاب إلى السطر التالي من التنفيذ، ويمكن إدخال أحد الأوامر التالية:

  • ‎c‎ أو ‎cont‎: لإكمال عملية التنفيذ حتى الوصول إلى نقطة الوقوف التالية أو حتى الانتهاء من تنفيذ البرنامج.
  • ‎n‎ أو ‎next‎: للتقدم خطوة إلى الأمام في التنفيذ إلى السطر التالي من الشيفرة.
  • ‎s‎ أو ‎step‎: للدخول إلى دالة ما، حيث تكون عملية التقدم افتراضيًا ضمن النطاق scope الذي نصحح الأخطاء ضمنه فقط، وتمكننا هذه العملية من الدخول ضمن دالة استدعتها الشيفرة التي نفحصها لمعاينة عملها من الداخل ومراقبة تعاملها مع البيانات المُمررة لها.
  • ‎o‎: للخروج من دالة حيث سيعود التنفيذ لخارجها إلى مكان استدعائها، وهو المكان الذي ستُرجع قيمة تنفيذ الدالة إليه، حيث يفيد هذا الأمر في العودة مباشرةً إلى خارج الدالة إلى المكان الذي كنا نعاينه قبل الدخول إليها.
  • ‎pause‎: لإيقاف التنفيذ مباشرةً مؤقتًا.

لنتقدم بتنفيذ البرنامج سطرًا تلو الآخر بتنفيذ الأمر ‎n‎ للانتقال إلى السطر التالي:

debug> n

نلاحظ تقدم التنفيذ إلى السطر الثالث:

break in badLoop.js:3
  1 let orders = [341, 454, 198, 264, 307];
  2 
> 3 let totalOrders = 0;
  4 
  5 for (let i = 0; i <= orders.length; i++) {

يتم تجاوز الأسطر الفارغة، لذا إذا قدّمنا علمية التنفيذ سطرًا آخر الآن بتنفيذ الأمر ‎n‎ مجددًا سينتقل التنفيذ إلى السطر الخامس:

break in badLoop.js:5
  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

يوقف التنفيذ الآن في بداية الحلقة، وإذا كانت الطرفية تدعم إظهار الألوان في الخرج سنلاحظ تحديد القيمة ‎0‎ ضمن التعليمة ‎let i = 0‎، حيث يحدد المنقح أي قسم من الشيفرة على وشك التنفيذ، ففي الحلقة ‎for‎ أول ما ينفذ هو إسناد القيمة لعداد الحلقة، وسنبدأ هنا بمعاينة القيم للمتغيرات لنحدد سبب الحصول على القيمة ‎NaN‎ بدلًا من القيمة العددية لمتغير المجموع ‎totalOrders‎، حيث أن قيمتي المتغيرين ‎totalOrders‎ و ‎i‎ تتغيران عند كل دورة للحلقة، وسنستفيد من ميزة الرصد والمراقبة التي يوفرها المنقح في مراقبة قيم هذين المتغيرين.

نبدأ بإعداد المراقبة لمتغير المجموع الكلي ‎totalOrders‎ بتنفيذ التعليمة التالية:

debug> watch('totalOrders')

لمراقبة أي متغير خلال تنقيح الأخطاء نستدعي الدالة ‎watch()‎ الذي يوفرها المنقح ونمرر لها سلسلة نصية تحوي على اسم المتغير الذي نريد مراقبته، وبعد الضغط على زر الإدخال ‎ENTER‎ وتنفيذ الدالة ‎watch()‎ سينتقل التنفيذ إلى سطر جديد دون ظهور أي خرج، وستظهر القيم التي نراقبها عند الانتقال للسطر التالي.

لنراقب أيضًا المتغير الآخر ‎i‎ بنفس الطريقة:

debug> watch('i')

سنشاهد الآن عملية المراقبة للمتغيرات السابقة، ننفذ الأمر ‎n‎ للانتقال خطوة للأمام وسيظهر لنا التالي:

break in badLoop.js:5
Watchers:
  0: totalOrders = 0
  1: i = 0

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

نلاحظ ظهور قيم المتغيرين اللذين نراقبهما ‎totalOrders‎ و ‎i‎ قبل الشيفرة حيث سيتم تحديث هذه القيم عند تغيرها، ونلاحظ أن المنقح يحدد حاليًا الخاصية ‎length‎ من التعليمة ‎orders.length‎، ما يعني أن الخطوة التالية هي التحقق من شرط إكمال التنفيذ للحلقة قبل إعادة تنفيذ التعليمات في جسم الحلقة، وبعدها ستُنفذ تعليمة زيادة قيمة عداد الحلقة ‎i++‎.

والآن نتقدم خطوة للأمام بتنفيذ الأمر ‎n‎ مجددًا للدخول إلى جسم الحلقة:

break in badLoop.js:6
Watchers:
  0: totalOrders = 0
  1: i = 0

  4 
  5 for (let i = 0; i <= orders.length; i++) {
> 6   totalOrders += orders[i];
  7 }
  8

ستُعدِّل التعليمة الحالية من قيمة المتغير ‎totalOrders‎، وسنلاحظ ذلك من تغير تلك القيمة ضمن قسم المراقبة في الأعلى.

والآن نتقدم خطوة إلى الأمام بتنفيذ ‎n‎ ليظهر لنا ما يلي:

Watchers:
  0: totalOrders = 341
  1: i = 0

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

نلاحظ أن قيمة متغير المجموع الكلي ‎totalOrders‎ تساوي قيمة أول عنصر من المصفوفة ‎341‎، والخطوة التالية الآن هي التحقق من شرط إكمال تنفيذ الحلقة، لذا ننفذ الأمر ‎n‎ لتعديل قيمة عداد الحلقة ‎i‎:

break in badLoop.js:5
Watchers:
  0: totalOrders = 341
  1: i = 1

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

إلى الآن قد تقدمنا عدة خطوات يدويًا ضمن الشيفرة لمراقبة التغير في قيم المتغيرات، لكن تلك الطريقة غير عملية حيث سنتعرف في الفقرة التالية على حل لهذه المشكلة باستخدام نقاط الوقوف، وسنُكمل حاليًا العمل بتقديم عملية التنفيذ يدويًا ومراقبة قيم المتغيرات للعثور على سبب المشكلة.

والآن نتقدم في التنفيذ 12 خطوة للأمام لنلاحظ الخرج التالي:

break in badLoop.js:5
Watchers:
  0: totalOrders = 1564
  1: i = 5

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

عدد القيم ضمن المصفوفة ‎orders‎ هو خمسة، ولكن قيمة عداد الحلقة ‎i‎ الحالية هي ‎5‎، وبما أننا نستخدم قيمة المتغير ‎i‎ للوصول إلى العنصر ضمن المصفوفة بالترتيب الحالي فالقيمة عند الترتيب ‎orders[5]‎ غير موجودة، وترتيب آخر قيمة ضمن المصفوفة ‎orders‎ هو ‎4‎، ما يعني أن محاولة الوصول للعنصر السادس باستخدام ‎orders[5]‎ سيعيد القيمة ‎undefined‎.

والآن نتقدم بالتنفيذ خطوة للأمام بتنفيذ الأمر ‎n‎:

break in badLoop.js:6
Watchers:
  0: totalOrders = 1564
  1: i = 5

  4 
  5 for (let i = 0; i <= orders.length; i++) {
> 6   totalOrders += orders[i];
  7 }
  8

وبالتقدم خطوة إضافية بتنفيذ ‎n‎ نلاحظ القيمة الجديدة للمتغير ‎totalOrders‎:

break in badLoop.js:5
Watchers:
  0: totalOrders = NaN
  1: i = 5

  3 let totalOrders = 0;
  4 
> 5 for (let i = 0; i <= orders.length; i++) {
  6   totalOrders += orders[i];
  7 }

لاحظنا بالاستفادة من عملية تنقيح الشيفرة ومراقبة قيم المتغيرين ‎totalOrders‎ و ‎i‎ أن الحلقة تُنفَّذ ستة مرات بدلًا من خمسة، وعندما تكون قيمة عداد الحلقة ‎i‎ هي ‎5‎ فمحاولة الوصول للعنصر الحالي ‎orders[5]‎ وإضافته للمتغير ‎totalOrders‎ ستجعل من قيمة المجموع تساوي ‎NaN‎، لأن قيمة العنصر السادس ‎orders[5]‎ الغير موجود ستكون ‎undefined‎، فإذًا المشكلة هي في شرط الحلقة ‎for‎ فبدلًا من التحقق من أن قيمة العداد ‎i‎ هي أصغر أو تساوي طول المصفوفة ‎orders‎ يجب أن نتحقق من أنها أصغر من الطول فقط.

وبعد أن حددنا المشكلة نخرج من المنقح ونصحح الخطأ ضمن الشيفرة ونعيد تنفيذ البرنامج ونتحقق من النتيجة، لكن أولًا ننفذ أمر الخروج ‎.exit‎ ثم نضغط زر الإدخال ‎ENTER‎:

debug> .exit

نخرج بذلك من وضع المنقح ونعود إلى الملف ‎badLoop.js‎ ونفتحه ضمن محرر النصوص ونعدل شرط حلقة ‎for‎ كالتالي:

...
for (let i = 0; i < orders.length; i++) {
...

نحفظ الملف ونخرج منه ونشغل البرنامج:

$ node badLoop.js

سنلاحظ ظهور قيمة المجموع الصحيحة ونكون بذلك حللنا المشكلة:

1564

نكون بذلك قد تعلمنا طريقة استخدام المنقح ودالة مراقبة المتغيرات ‎watch‎ الخاصة به لاستكشاف وتحديد الأخطاء أثناء التنفيذ!

وسنتعلم الآن في الفقرة التالية كيف يمكننا الاستفادة من نقاط الوقوف لتنقيح الأخطاء ضمن البرنامج دون الحاجة لتقديم التنفيذ يدويًا سطرًا تلو الآخر.

استخدام نقاط الوقوف Breakpoints

تتألف البرامج في نود عادة من عدة وحدات برمجية يتشابك عملها مع بعضها بعضًا، لذا محاولة تنقيح الأخطاء سطرًا تلو الآخر كما فعلنا في الفقرة السابقة أمر صعب وغير مجدي في التطبيقات الكبيرة المعقدة، وهنا يأتي دور نقاط الوقوف breakpoints لحل تلك المشكلة.

تسمح نقاط الوقوف بتخطي التنفيذ إلى السطر الذي نريده مباشرةً وإيقاف البرنامج لمعاينة حالته آنذاك، حيث لإضافة نقطة وقوف في نود نضيف الكلمة المحجوزة ‎debugger‎ ضمن الشيفرة مباشرةً، ويمكننا بعدها وخلال عملية التنقيح التنقل بين نقاط الوقوف ضمن الشيفرة بتنفيذ الأمر ‎c‎ في طرفية التنقيح بدلًا من الأمر ‎n‎ السابق، ويمكننا إضافة المراقبة للتعليمات التي نرغب بها عند نقاط الوقوف تلك.

سنتعرف على طريقة استخدام نقاط الوقوف بمثال عن برنامج يقرأ قائمة من الجمل ويستخرج منها الكلمة الأكثر تكرارًا ويعيدها لنا، لذلك سنُنشئ لهذا المثال ثلاث ملفات، الأول هو ملف يحوي الجمل النصية ‎sentences.txt‎ التي سيعالجها البرنامج، حيث سنضيف داخله كمثال أول فقرة من مقال عن سمكة قرش الحوت من موسوعة بريتانيكا Britannica بعد إزالة علامات الترقيم منها، لذلك ننشئ الملف ونفتحه ضمن محرر النصوص:

$ nano sentences.txt

ونكتب داخله النص التالي:

Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish
Whale sharks are found in marine environments worldwide but mainly in tropical oceans
They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks
The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet
Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length
The body coloration is distinctive
Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body

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

$ nano textHelper.js

ونضيف ثلاث دوال لمعالجة النص ضمن الملف ‎sentences.txt‎ الأول لقراءة الملف:

const fs = require('fs');

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  return sentences;
};

نستورد الوحدة البرمجية ‎fs‎ من نود لنتمكن من قراءة الملف، بعدها نضيف الدالة ‎readFile()‎ التي تستخدم التابع ‎readFileSync()‎ لتحميل محتوى الملف ‎sentences.txt‎ ككائن مخزن مؤقت ‎Buffer‎ ثم تستدعي منه التابع ‎toString()‎ لتحويل المحتوى إلى سلسلة نصية.

نضيف بعدها دالة لتجزئة السلسلة نصية السابقة إلى مصفوفة من الكلمات كالتالي:

...

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  return words;
};

استفدنا من التوابع ‎split()‎ و ‎join()‎ و ‎map()‎ لتحويل السلسلة النصية إلى مصفوفة من الكلمات الموجودة ضمنها، وحولنا حالة كل كلمة منها إلى أحرف صغيرة لتسهيل عملية المقارنة بينها وإحصائها.

أما الدالة الثالثة والأخيرة فستحصي تكرار كل كلمة ضمن مصفوفة الكلمات السابقة ويعيد كل الكلمات مع تكراراتها ضمن كائن يعبر عن النتيجة كالتالي:

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  return map;
};

أنشأنا كائنًا جديدًا بالاسم ‎map‎ يحوي الكلمات ضمن النص كمفاتيح وعدد مرات تكرارها كقيم لها، ثم مررنا على عناصر مصفوفة الكلمات وأضفناها إلى ذلك الكائن إن تكن موجودة أو زدنا قيمة تكرارها قيمة واحدة.

وأخيرًا لنصدر تلك الدوال لنتمكن من استخدامها ضمن الوحدات البرمجية الأخرى:

...

module.exports = { readFile, getWords, countWords };

نحفظ الملف ونخرج منه، والآن سننشئ الملف الثالث والأخير ضمن المثال هو الملف الأساسي الذي سيستعين بالدوال ضمن الوحدة البرمجية السابقة ‎textHelper.js‎ لاستخراج أكثر كلمة تكرارًا من النص.

نبدأ بإنشاء الملف ‎index.js‎ ثم نفتحه ضمن محرر النصوص:

$ nano index.js

نستورد الوحدة البرمجية ‎textHelpers.js‎ كالتالي:

const textHelper = require('./textHelper');

وننشئ مصفوفة جديدة تحتوي على بعض الكلمات المكررة الشائعة التي نرغب بتجاهلها مثل حروف العطف والجر والضمائر وبعض الصفات، تدعى الكلمات الشائعة أو stop words:

...

const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

بهذه الطريقة سنحصل على كلمات ذات معاني من ضمن النص الذي نعالجه بدلًا من الحصول على كلمات مثل أدوات التعريف التي تتكرر كثيرًا مثل the‎ و a‎.

نبدأ باستخدام الدوال المساعدة من الوحدة ‎textHelper.js‎ لقراءة النص واستخراج الكلمات منه وإحصاء مرات التكرار لكل منها كالتالي:

...

let sentences = textHelper.readFile();
let words = textHelper.getWords(sentences);
let wordCounts = textHelper.countWords(words);

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

...

let max = -Infinity;
let mostPopular = '';

Object.entries(wordCounts).forEach(([word, count]) => {
  if (stopwords.indexOf(word) === -1) {
    if (count > max) {
      max = count;
      mostPopular = word;
    }
  }
});

console.log(`The most popular word in the text is "${mostPopular}" with ${max} occurrences`);

استخدمنا التابع ‎Object.entries()‎ لتحويل المفاتيح والقيم ضمن الكائن ‎wordCounts‎ إلى مصفوفة، ثم استخدمنا التابع ‎forEach()‎ وداخله عبارة شرطية لاختبار قيمة التكرار للكلمة الحالية مع أعلى قيمة تكرار شاهدناها سابقًا.

والآن نحفظ الملف ونخرج منه وننفذه كالتالي:

$ node index.js

نلاحظ ظهور النتيجة التالية:

The most popular word in the text is "whale" with 1 occurrences

لكن الجواب الذي ظهر خاطئ فنلاحظ تكرار الكلمة ‎whale‎ أكثر من مرة ضمن النص في الملف ‎sentences.txt‎، وهذه المرة قد يكون السبب في أحد الدوال العديدة المستخدمة في البرنامج، فقد تكون المشكلة في عملية قراءة محتوى الملف كاملًا، أو خلال معالجته وتحويله لمصفوفة الكلمات، أو خلال عملية توليد كائن إحصاء مرات التكرار للكلمات، أو قد يكون الخطأ في خوارزمية تحديد الكلمة الأكثر تكرارًا.

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

لنبدأ بإضافة نقاط وقوف ضمن كل من التوابع المساعدة في الملف ‎textHelper.js‎ بإضافة الكلمة المحجوزة ‎debugger‎ ضمن الشيفرة في تلك الأماكن، لذا نفتح الملف ‎textHelper.js‎ ضمن محرر النصوص ونضيف أول نقطة وقوف ضمن التابع ‎readFile()‎ كالتالي:

...

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  debugger;
  return sentences;
};

...

بعدها نضيف نقطة وقوف أخرى ضمن الدالة ‎getWords()‎:

...

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  debugger;
  return words;
};

...

وأخيرًا نضيف نقطة وقوف للدالة ‎countWords()‎ كالتالي:

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  debugger;
  return map;
};

...

نحفظ الملف ونخرج منه، ونبدأ جلسة تنقيح الأخطاء ومع أن كل نقاط الوقوف التي أضفناها موجودة ضمن الملف ‎textHelpers.js‎ لكن عملية تنقيح الأخطاء ستبدأ من الملف الرئيسي للتطبيق ‎index.js‎، لذا ندخل لجلسة تنقيح الأخطاء من ذلك الملف كما تعلمنا سابقًا كالتالي:

$ node inspect index.js

ليظهر لنا التالي:

< Debugger listening on ws://127.0.0.1:9229/b2d3ce0e-3a64-4836-bdbf-84b6083d6d30
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in index.js:1
> 1 const textHelper = require('./textHelper');
  2 
  3 const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

هذه المرة سننفذ الأمر ‎c‎ وهو اختصار للكلمة continue وتعني إكمال التنفيذ لينتقل بذلك المنقح مباشرة إلى أول نقطة وقوف يصل إليها تنفيذ الشيفرة، وبعد الضغط على زر الإدخال ‎ENTER‎ لتنفيذ الأمر يظهر التالي:

break in textHelper.js:6
  4   let data = fs.readFileSync('sentences.txt');
  5   let sentences = data.toString();
> 6   debugger;
  7   return sentences;
  8 };

نلاحظ كم من الوقت قد وفرنا في هذه العملية حيث توجهنا مباشرة إلى أول نقطة وقوف، ولنتأكد من أن هذه الدالة تعمل بشكل سليم وتقرأ محتوى الملف النصي كاملًا وتعيده، سنراقب المتغير ‎sentences‎ لنعاين قيمته ونتأكد من صحة القيمة التي تعيدها الدالة:

debug> watch('sentences')

نتقدم بالتنفيذ خطوة للأمام فقط بتنفيذ الأمر ‎n‎ لنعاين قيمة المتغير ‎sentences‎:

break in textHelper.js:7
Watchers:
  0: sentences =
    'Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish\n' +
      'Whale sharks are found in marine environments worldwide but mainly in tropical oceans\n' +
      'They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks\n' +
      'The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet\n' +
      'Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length\n' +
      'The body coloration is distinctive\n' +
      'Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body\n'

  5   let sentences = data.toString();
  6   debugger;
> 7   return sentences;
  8 };
  9

تبدو القيمة صحيحة ولا مشاكل في عملية قراءة محتوى الملف إذًا فالمشكلة في مكان آخر.

لننتقل إلى نقطة الوقوف التالية بتنفيذ الأمر ‎c‎ مجددًا ليظهر ما يلي:

break in textHelper.js:15
Watchers:
  0: sentences =
    ReferenceError: sentences is not defined
        at eval (eval at getWords (your_file_path/debugger/textHelper.js:15:3), <anonymous>:1:1)
        at Object.getWords (your_file_path/debugger/textHelper.js:15:3)
        at Object.<anonymous> (your_file_path/debugger/index.js:7:24)
        at Module._compile (internal/modules/cjs/loader.js:1125:14)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
        at Module.load (internal/modules/cjs/loader.js:983:32)
        at Function.Module._load (internal/modules/cjs/loader.js:891:14)
        at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
        at internal/main/run_main_module.js:17:47

 13   let words = flatSentence.split(' ');
 14   words = words.map((word) => word.trim().toLowerCase());
>15   debugger;
 16   return words;
 17 };

رسالة الخطأ التي ظهرت سببها مراقبتنا سابقًا لقيمة المتغير ‎sentences‎ الذي لم يعد موجودًا الآن ضمن نطاق تنفيذ الدالة الحالية، حيث تبقى عملية المراقبة للمتغير طول مدة جلسة تنقيح الأخطاء، لذا سيتكرر ظهور رسالة الخطأ تلك ما دام المتغير لا يمكن الوصول إليه من مكان التنفيذ الحالي.

ويمكننا حل تلك المشكلة بإيقاف مراقبة المتغير باستخدام الدالة ‎unwatch()‎ لإيقاف مراقبة المتغير ‎sentences‎ بتنفيذ التعليمة التالية:

debug> unwatch('sentences')

لن تظهر أي رسالة عند تنفيذ التعليمة السابقة، والآن لنعود إلى الدالة ‎getWords()‎ ونتأكد من صحة القيمة التي تعيدها وهي قائمة من كلمات النص السابق، لهذا نضيف مراقبة للمتغير ‎words‎ كالتالي:

debug> watch('words')

وننتقل لتنفيذ السطر التالي بتنفيذ التعليمة ‎n‎ ونعاين قيمة المتغير ‎words‎، ونلاحظ ظهور ما يلي:

break in textHelper.js:16
Watchers:
  0: words =
    [ 'whale',
      'shark',
      'rhincodon',
      'typus',
      'gigantic',
      'but',
      'harmless',
      ...
      'metres',
      '39',
      'feet',
      'in',
      'length',
      '',
      'the',
      'body',
      'coloration',
      ... ]

 14   words = words.map((word) => word.trim().toLowerCase());
 15   debugger;
>16   return words;
 17 };
 18

لم يُظهر منقح الأخطاء محتوى المصفوفة كاملةً بسبب طولها وصعوبة قراءتها كاملة، ولكن ما ظهر يكفي ليؤكد أن محتوى النص ضمن المتغير ‎sentences‎ تم تجزئته إلى كلمات بحالة أحرف صغيرة، أي أن الدالة ‎getWords()‎ تعمل بشكل سليم.

والآن ننتقل لمعاينة الدالة الثالثة وهي ‎countWords()‎، ولكن أولًا سنزيل المراقبة للمصفوفة ‎words‎ كي لا يظهر لنا رسالة خطأ كما حدث سابقًا عند الانتقال إلى نقطة الوقوف التالية كالتالي:

debug> unwatch('words')

ثم ننفذ الأمر ‎c‎ لينتقل التنفيذ إلى نقطة الوقوف التالية ويظهر ما يلي:

break in textHelper.js:29
 27   });
 28 
>29   debugger;
 30   return map;
 31 };

سنتأكد ضمن هذه الدالة من احتواء المتغير ‎map‎ على كل الكلمات السابقة مع قيم تكرارها، لذا نبدأ مراقبة المتغير ‎map‎ كالتالي:

debug> watch('map')

ثم ننتقل بالتنفيذ إلى السطر التالي بتنفيذ الأمر ‎n‎ ليظهر لنا ما يلي:

break in textHelper.js:30
Watchers:
  0: map =
    { 12: NaN,
      14: NaN,
      15: NaN,
      18: NaN,
      39: NaN,
      59: NaN,
      whale: 1,
      shark: 1,
      rhincodon: 1,
      typus: NaN,
      gigantic: NaN,
      ... }

 28
 29   debugger;
>30   return map;
 31 };
 32

على ما يبدو أن هذه الدالة هي سبب المشكلة وعملية إحصاء تكرار الكلمات خاطئة، ولمعرفة سبب الخطأ يجب أن نعاين عمل هذه الدالة ضمن حلقة المرور على عناصر المصفوفة ‎words‎، لذا سنعدل أماكن نقاط الوقوف الحالية.

نبدأ بالخروج من منقح الأخطاء بتنفيذ الأمر التالي:

debug> .exit

ثم نفتح الملف ‎textHelper.js‎ ضمن محرر النصوص لنعدل نقاط الوقوف ضمنه:

$ nano textHelper.js

بما أننا تأكدنا من صحة عمل الدالتين ‎readFile()‎ و ‎getWords()‎ سنزيل نقاط الوقوف من داخلهما، ونزيل نقطة الوقوف من نهاية الدالة ‎countWords()‎ ونضيف نقطتي وقوف جديدتين في بداية ونهاية الدالة ‎forEach()‎، ليصبح الملف ‎textHelper.js‎ كالتالي:

...

const readFile = () => {
  let data = fs.readFileSync('sentences.txt');
  let sentences = data.toString();
  return sentences;
};

const getWords = (text) => {
  let allSentences = text.split('\n');
  let flatSentence = allSentences.join(' ');
  let words = flatSentence.split(' ');
  words = words.map((word) => word.trim().toLowerCase());
  return words;
};

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    debugger;
    if (word in map) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
    debugger;
  });

  return map;
};

...

نحفظ الملف ونخرج منه ثم نبدأ جلسة تنقيح أخطاء جديدة كالتالي:

$ node inspect index.js

كي نحدد سبب المشكلة يجب أن نراقب عدة قيم، أولها قيمة الكلمة الحالية ‎word‎ المُمررة كمعامل من قبل تابع حلقة التكرار ‎forEach()‎ كالتالي:

debug> watch('word')

لا تقتصر ميزة المراقبة ضمن جلسة تنقيح الأخطاء على المتغيرات فحسب، بل يمكن مراقبة قيم تعابير جافاسكربت البرمجية المستخدمة ضمن الشيفرة، كأن نراقب قيمة تنفيذ التعليمة الشرطية ‎word in map‎ والتي تحدد ما إذا كانت الكلمة الحالية موجودة مسبقًا، ويمكن مراقبتها بتنفيذ التالي:

debug> watch('word in map')

لنضيف مراقبة لقيمة تكرار الكلمة الحالية ضمن متغير النتيجة ‎map‎ كالتالي:

debug> watch('map[word]')

لا تقتصر ميزة المراقبة على التعابير البرمجية الموجودة ضمن الشيفرة فحسب، بل يمكن إضافة أي تعابير برمجية نريدها ليتم تنفيذها ومراقبة قيمتها، لذا سنستفيد من هذه الميزة ونضيف مراقبة لقيمة طول الكلمة الحالية ضمن المتغير ‎word‎:

debug> watch('word.length')

بعد أن انتهينا من إضافة القيم التي نريد مراقبتها أثناء التنفيذ سننفذ الأمر ‎c‎ ونراقب كيف تعالج الدالة أول كلمة من مصفوفة الكلمات ضمن الحلقة داخل الدالة ‎countWords()‎، ليظهر لنا ما يلي:

break in textHelper.js:20
Watchers:
  0: word = 'whale'
  1: word in map = false
  2: map[word] = undefined
  3: word.length = 5

 18   let map = {};
 19   words.forEach((word) => {
>20     debugger;
 21     if (word in map) {
 22       map[word] = 1;

الكلمة الأولى التي يتم معالجتها هي ‎whale‎ ولا يحوي الكائن ‎map‎ على مفتاح للكلمة ‎whale‎ لأنه فارغ، لذا قيمة المراقبة للكلمة الحالية ‎whale‎ ضمن الكائن ‎map‎ كما نلاحظ هي ‎undefined‎، وطول الكلمة الحالية ‎whale‎ هو ‎5‎، وهذه القيمة تحديدًا لا تفيدنا في البحث عن سبب الخطأ، ولكننا أضفناها لنتعلم كيف يمكن حساب ومراقبة أي تعبير برمجي خلال جلسة تنقيح الأخطاء.

والآن ننفذ التعليمة ‎c‎ لنرى ماذا سيحدث في نهاية تنفيذ الدورة الحالية ليظهر لنا ما يلي:

break in textHelper.js:26
Watchers:
  0: word = 'whale'
  1: word in map = true
  2: map[word] = NaN
  3: word.length = 5

 24       map[word] += 1;
 25     }
>26     debugger;
 27   });
 28

أصبحت قيمة العبارة ‎word in map‎ صحيحة ‎true‎ بسبب إضافة مفتاح للكلمة الحالية ‎whale‎ ضمن الكائن ‎map‎، ولكن قيمة المفتاح ‎whale‎ ضمن الكائن ‎map‎ هي ‎NaN‎ ما يدل على وجود مشكلة ما، وتحديدًا في العبارة الشرطية ‎if‎ ضمن الدالة ‎countWords()‎، فوظيفتها هي تحديد فيما إذا كنا سنضيف مفتاحًا جديدًا للكلمة الحالية إذا لم تكن موجودة سابقًا، أو إضافة واحد لقيمة المفتاح إن كان موجودًا مسبقًا، والصحيح هو تعيين القيمة ‎map[word]‎ إلى ‎1‎ إذا لم تكن الكلمة ‎word‎ موجودة كمفتاح ضمن ‎map‎، بينما حاليًا نحن نضيف قيمة واحد في حال العثور على ‎word‎ وهو عكس المطلوب.

وكما لاحظنا في بداية الحلقة كانت قيمة التكرار للكلمة الحالية ‎map["whale"]‎ غير موجودة ‎undefined‎، وفي جافاسكربت إذا حاولنا إضافة واحد إلى تلك القيمة ‎undefined + 1‎ سينتج عن تلك العملية القيمة ‎NaN‎ وهو ما ظهر بالفعل، ولتصحيح هذه المشكلة يمكننا تعديل الشرط ضمن ‎if‎، فبدلًا من أن يكون ‎word in map‎ ننفي هذه العبارة لتصبح كالتالي ‎!(word in map)‎، حيث يُستخدم الرمز ‎!‎ لنفي العبارات المنطقية فيصبح الشرط صحيحًا إذا لم يحتوي الكائن ‎map‎ على مفتاح للقيمة ‎word‎.

والآن لننفذ هذا التعديل ضمن الدالة ‎countWords()‎ ونختبرها مجددًا، لكن نخرج أولًا من جلسة تنقيح الأخطاء كالتالي:

debug> .exit

ونفتح الملف ‎textHelper.js‎ مجددًا ضمن محرر النصوص:

$ nano textHelper.js

نعدل الدالة ‎countWords()‎ بالشكل التالي:

...

const countWords = (words) => {
  let map = {};
  words.forEach((word) => {
    if (!(word in map)) {
      map[word] = 1;
    } else {
      map[word] += 1;
    }
  });

  return map;
};

...

نحفظ الملف ونخرج منه، وننفذ البرنامج ونراقب النتيجة:

$ node index.js

تظهر لنا النتيجة التالية هذه المرة:

The most popular word in the text is "whale" with 3 occurrences

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

وتعلمنا أيضًا كيف يمكن إضافة نقاط الوقوف باستخدام الكلمة ‎debugger‎ وإعداد مراقبة لمختلف القيم والعبارات البرمجية لمراقبة حالة البرنامج أثناء التنفيذ وكل ذلك من سطر الأوامر، ولكن لتوفير تجربة استخدام أسهل يمكن إجراء العملية نفسها عبر واجهة مستخدم مرئية، وهذا ما سنتعرف عليه في الفقرة التالية.

سنتعلم في الفقرة التالية طريقة استخدام منقح الأخطاء من أدوات المطور في متصفح جوجل كروم، حيث سنبدأ جلسة لتنقيح الأخطاء في نود كما فعلنا سابقًا، وسنستعمل صفحة مخصصة من واجهة متصفح كروم لتعيين نقاط الوقوف وعمليات المراقبة من واجهة مرئية بدلًا من سطر الأوامر.

تنقيح الأخطاء في نود باستخدام أدوات المطور في كروم

تعد أدوات المطور في متصفح كروم من أشهر أدوات نتقيح الأخطاء لشيفرة جافاسكربت عمومًا ونود خصوصًا ضمن متصفح الويب، وذلك لأن محرك جافاسكربت المستخدم من قبل نود V8 هو نفسه المستخدم في متصفح كروم، لذا فالتكامل بينهما يوفر تجربة مرنة لتنقيح الأخطاء.

سنطبق في هذه الفقرة على مثال بسيط وهو خادم HTTP في نود مهمته إعادة قيمة بصيغة JSON كرد على الطلبات الواردة، وسنستخدم لاحقًا منقح الأخطاء لإعداد نقاط الوقوف ومراقبة عمل ذلك الخادم وتحديدًا كيف يتم توليد قيمة الرد على الطلبات الواردة، وللمزيد حول عملية إنشاء الخادم، راجع مقالة إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP.

نبدأ بإنشاء ملف جافاسكربت جديد بالاسم ‎server.js‎ سيحوي على برنامج الخادم ونفتح الملف ضمن محرر النصوص كالتالي:

$ nano server.js

مهمة الخادم هي إعادة العبارة ‎Hello World‎ بصيغة JSON ضمن الرد، حيث سيحوي على مصفوفة لعدة ترجمات لتلك العبارة ليختار إحداها عشوائيًا ويعيدها ضمن جسم الرد بصيغة JSON، وسيستمع الخادم إلى الطلبات الواردة على العنوان المحلي ‎localhost‎ وعلى المنفذ رقم ‎:8000‎.

والآن نبدأ بإضافة شيفرة البرنامج كما يلي:

const http = require("http");

const host = 'localhost';
const port = 8000;

const greetings = ["Hello world", "Hola mundo", "Bonjour le monde", "Hallo Welt", "Salve mundi"];

const getGreeting = function () {
  let greeting = greetings[Math.floor(Math.random() * greetings.length)];
  return greeting
}

استوردنا الوحدة برمجية ‎http‎ والتي تساعد في إعداد خادم HTTP، ثم وضعنا قيم عنوان الخادم ورقم المنفذ ضمن المتغيرين ‎host‎ و ‎port‎ لاستخدامها لاحقًا لتشغيل الخادم، ثم عرفنا مصفوفة العبارات ‎greetings‎ والتي تحوي على جميع العبارات الممكن إرسالها من قبل الخادم لتختار الدالة ‎getGreeting()‎ إحداها عشوائيًا ويعيده.

والآن سنضيف دالة معالجة طلبات HTTP القادمة للخادم وشيفرة بدء تشغيل الخادم كالتالي:

...

const requestListener = function (req, res) {
  let message = getGreeting();
  res.setHeader("Content-Type", "application/json");
  res.writeHead(200);
  res.end(`‎{"message": "${message}"}‎`);
};

const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`‎Server is running on http://${host}:${port}‎`);
});

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

$ node --inspect server.js

ملاحظة: نلاحظ الفرق بين أمر بدء منقح الأخطاء الخاص بنود من سطر الأوامر وبين أمر منقح الأخطاء الخاص بكروم، حيث ننفذ الأمر ‎inspect‎ للأول، أما للثاني نمرر الخيار ‎--inspect‎.

وبعد تشغيل منقح الأخطاء سنلاحظ ظهور ما يلي:

Debugger listening on ws://127.0.0.1:9229/996cfbaf-78ca-4ebd-9fd5-893888efe8b3
For help, see: https://nodejs.org/en/docs/inspector
Server is running on http://localhost:8000

يمكننا الآن فتح متصفح جوجل كروم أو كروميوم Chromium والذهاب للعنوان ‎chrome://inspect‎ من شريط العنوان في الأعلى، ويمكن أيضًا استعمال منقح الأخطاء لمتصفح مايكروسوفت إيدج Microsoft Edge ولكن بالذهاب إلى العنوان ‎edge://inspect‎ بدلًا من العنوان السابق.

وبعد الذهاب لذلك العنوان ستظهر لنا الصفحة التالية:

صفحة DevTools

نذهب لقسم الأجهزة Devices ونضغط على أمر فتح أداوت المطور الخاصة بنود "Open dedicated DevTools for Node" لتظهر لنا نافذة منفصلة كالتالي:

فتح أداوت المطور الخاصة بنود

يمكننا الآن تنقيح أخطاء برنامج نود السابق بواسطة كروم، لذلك نذهب إلى تبويب المصادر Sources ونوسع قسم شجرة الملفات الظاهر على اليسار ونختار منه ملف البرنامج الخاص بنا وهو ‎server.js‎:

اختيار ملف البرنامج ‎server.js‎

ونضيف نقطة وقوف ضمن الشيفرة التي تظهر، حيث نريد التوقف بعد أن يختار البرنامج عبارة الترحيب التي سيعيدها ضمن الرد لنعاينها، لذلك يمكننا الضغط مباشرة على رقم السطر 10 لتظهر نقطة حمراء بجانبه ما يدل على إضافة نقطة وقوف في هذا السطر، وهو ما نلاحظه من قائمة نقاط الوقوف في اللوحة على اليمين:

إضافة نقطة وقوف في السطور

لنراقب الآن عبارة برمجية، حيث يمكننا ذلك من اللوحة على اليمين وتحديدًا بجانب عنوان قسم المراقبة Watch بالضغط على علامة الزائد "+"، ونضيف اسم المتغير ‎greeting‎ لنراقب قيمته أثناء التنفيذ ثم نضغط على زر الإدخال ‎ENTER‎.

والآن لنبدأ بتنقيح البرنامج، فنذهب ضمن نافذة المتصفح إلى عنوان الذي يستمع إليه الخادم ‎http://localhost:8000‎ وبعد الضغط على زر الإدخال ‎ENTER‎ للذهاب إلى ذلك العنوان سنلاحظ عدم ظهور أي رد مباشرة بل ستظهر لنا نافذة تنقيح الأخطاء في الواجهة مجددًا، وفي حال لم تظهر النافذة يمكن الذهاب إليها يدويًا لنلاحظ ظهور ما يلي:

الانتقال للبدأ بتنقيح البرنامج في Node.js

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

ولمتابعة تنفيذ الشيفرة نضغط على زر المتابعة الموجود في اللوحة على الجانب الأيمن فوق العبارة "Paused on breakpoint" والتي تعني توقف التنفيذ عند نقطة الوقوف، وبعد اكتمال التنفيذ ستلاحظ ظهور رد بصيغة JSON ضمن نافذة المتصفح التي تواصلنا منها مع الخادم:

{"message": "Hello world"}

نلاحظ أننا لم نضيف أي عبارات ضمن الشيفرة أو نعدل عليها لإضافة نقاط الوقوف، وهي الفائدة التي تقدمها أدوات تنقيح الأخطاء من الواجهة المرئية مثل كروم، وهو الخيار الأفضل لمن لا يرغب بالتعامل مع سطر الأوامر ويفضل التعامل مع الواجهات المرئية.

ختامًا

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

يمكن الاستعانة بمنقح الأخطاء ما يسهل عملية استكشاف أخطاء التنفيذ ضمن البرنامج ومعاينة حالته، ما يوفر من وقت التطوير وخصوصًا وقت حل المشكلات وإصلاح الأخطاء.

ويمكن الرجوع إلى توثيق نود الرسمي عن أدوات تنقيح الأخطاء أو دليل أدوات المطور من كروم ودليل أدوات المطور لتنقيح شيفرة جافاسكربت.

تُعَد الأخطاء البرمجية أمرًا شائعًا في المجال البرمجي، وللتعرف أكثر عليها وعلى كيفية التعامل معها عامةً ننصحك بالاطلاع على الفيديو الآتي:

ترجمة -وبتصرف- للمقال How To Debug Node.js with the Built-In Debugger and Chrome DevTools لصاحبه Stack Abuse.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...