كيف تُصَحِّح أخطاء JavaScript مثل المحترفين

كيف تُصَحِّح أخطاء JavaScript مثل المحترفين

تعلّم JavaScript شيء، وتعلّم تصحيح أخطائها بسرعة وذكاء شيء آخر تمامًا. كثيرون يكتبون كودًا “يبدو صحيحًا”، ثم يقضون ساعة أو ساعتين في مطاردة خطأ صغير: متغير غير معرّف، Promise لم يُنتظر، DOM لا يتحدث كما يجب، أو حالة تتغير في مكان غير متوقع. هنا تظهر مهارة الـ Debugging كأحد أهم الفوارق بين مطوّر عادي ومطوّر واثق من نفسه.

في هذا المقال، لن نكتفي بشرح الأدوات، بل سنبني عقلية عملية تساعدك على فهم المشكلة من جذورها. ستتعلم كيف تقرأ الرسائل الخطأ، وكيف تستخدم console وDevTools بذكاء، وكيف تتعامل مع الأخطاء في المتصفح وNode.js، وكيف تكتشف المشاكل الصامتة التي لا تُظهر لك أي رسالة واضحة. وسنضيف أمثلة واقعية وجداول تساعدك على الرجوع بسرعة عندما تتعطل الأمور.


لماذا يبدو Debugging صعبًا في JavaScript؟

السبب ليس أن JavaScript “معقدة جدًا”، بل لأنها لغة مرنة إلى درجة كبيرة. هذه المرونة الجميلة تأتي أحيانًا بثمن: أخطاء غير متوقعة، تحويلات نوعية صامتة، سلوك غير واضح في this، وتوقيت غير مباشر بسبب الـ event loop والـ asynchronous code.

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

  1. يحدد المشكلة بدقة.

  2. يعيد إنتاجها.

  3. يضيّق النطاق.

  4. يقرأ الرسالة أو الأثر.

  5. يختبر فرضية واحدة في كل مرة.

  6. يصل للحل ثم يتحقق منه.

هذه هي الفكرة الأساسية: لا تلاحق الخطأ، بل حاصر السلوك.


العقلية الصحيحة قبل الأدوات

قبل أي console.log أو breakpoint، اسأل نفسك:

  • هل المشكلة تحدث دائمًا أم أحيانًا؟

  • هل هي مرتبطة ببيانات معينة؟

  • هل ظهرت بعد تعديل محدد؟

  • هل هي في المتصفح فقط أم في Node.js أيضًا؟

  • هل هي خطأ في المنطق أم خطأ في الواجهة أم مشكلة أداء؟

الفرق بين مطوّر مرتبك ومطوّر محترف هو أن الثاني لا يقول: “الكود لا يعمل”، بل يقول: “الزر يضغط، لكن الطلب لا يُرسل إلا بعد إعادة تحميل الصفحة”. هذا الوصف الدقيق يختصر نصف الحل.


القاعدة الذهبية الأولى: أعد إنتاج الخطأ

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

مثال على إعادة إنتاج مشكلة

function calculateDiscount(price, discount) {
  return price - price * discount;
}

console.log(calculateDiscount(100, "0.2"));

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


كيف تقرأ رسائل الخطأ بذكاء

رسالة الخطأ ليست عدوك. هي إشارة. والمطور الجيد لا يقرأ الرسالة فقط، بل يقرأ:

  • نوع الخطأ

  • الملف

  • السطر

  • الـ stack trace

  • السياق الذي أدى إليه

أشهر أنواع الأخطاء

نوع الخطأ

المعنى

مثال شائع

كيف تتعامل معه

ReferenceError

استخدام متغير غير معرّف

x is not defined

تحقق من الاسم والنطاق

TypeError

تنفيذ عملية غير صالحة على نوع بيانات

cannot read property of undefined

تحقق من القيمة قبل الاستخدام

SyntaxError

خطأ في الصياغة

قوس ناقص أو فاصلة مفقودة

راجع البنية

RangeError

قيمة خارج النطاق المقبول

Maximum call stack size exceeded

افحص التكرار أو العمق

URIError

خطأ في التعامل مع URI

decodeURIComponent على نص غير صالح

تحقق من المدخلات

EvalError

مرتبط بـ eval أو سياق غير مناسب

نادر الاستخدام

تجنبه غالبًا

مثال على TypeError

const user = null;
console.log(user.name);

الخطأ هنا واضح عند القراءة: لا يمكنك الوصول إلى name على قيمة null. الحل ليس فقط “إصلاح السطر”، بل فهم لماذا أصبحت user فارغة من الأساس.

حل آمن:

const user = null;
console.log(user?.name);

لكن انتبه: optional chaining لا يُصلح المنطق، بل يمنع الانفجار فقط. إن كانت القيمة يجب أن تكون موجودة، فالأفضل معالجة السبب.


استخدم console بذكاء، لا بعشوائية

كثيرون يضعون عشرات console.log ثم ينسون إزالتها. هذا يربكهم أكثر مما يساعدهم. استخدم الـ console بطريقة منظمة.

ما الذي تستخدمه ومتى؟

الأداة

الاستخدام المناسب

console.log()

عرض قيمة عادية

console.warn()

تنبيه غير قاتل

console.error()

خطأ واضح

console.table()

عرض مصفوفات أو كائنات بشكل منظم

console.trace()

معرفة مسار الاستدعاء

console.group()

تنظيم السجل في مجموعات

console.assert()

التحقق من شرط معيّن

مثال عملي

const users = [
  { id: 1, name: "Aya", active: true },
  { id: 2, name: "Yassine", active: false },
  { id: 3, name: "Sara", active: true },
];

console.table(users);

هذا أفضل بكثير من طباعة كل عنصر على سطر منفصل. ستقرأ البيانات بسرعة وتلاحظ أي شيء غير طبيعي.

مثال على console.trace()

function stepOne() {
  stepTwo();
}

function stepTwo() {
  console.trace("Trace from stepTwo");
}

stepOne();

هذه الطريقة مفيدة جدًا عندما تريد معرفة من استدعى دالة معيّنة، خاصة في التطبيقات الكبيرة.


استخدم DevTools كأنها مختبر، لا كنافذة عرض

أغلب المطورين يعرفون أن DevTools موجودة، لكن قلة فقط تستخدمها بالكامل. لو أتقنتها، ستوفر ساعات كل أسبوع.

أهم الأجزاء في DevTools

الجزء

فائدته

Elements

فحص DOM وCSS

Console

تنفيذ أوامر ومراجعة الأخطاء

Sources

وضع breakpoints وتتبع التنفيذ

Network

مراقبة الطلبات والاستجابات

Application

التخزين المحلي، الكوكيز، الـ storage

Performance

تحليل البطء

Memory

تتبع تسرب الذاكرة


كيف تستخدم Breakpoints بشكل احترافي

الـ breakpoint أقوى من مئات console.log. بدل أن تطبع كل شيء، أوقف التنفيذ في اللحظة المناسبة.

متى تضيف breakpoint؟

  • قبل المكان الذي تعتقد أن القيمة تتغير فيه

  • داخل loop مشبوه

  • قبل أو بعد await

  • عند استقبال بيانات من API

  • عند click handler لا يعمل كما يجب

مثال

function processOrder(order) {
  const total = order.items.reduce((sum, item) => sum + item.price, 0);
  return total * 1.2;
}

إذا كانت النتيجة خاطئة، ضع breakpoint داخل الدالة وتحقق من:

  • قيمة order

  • محتوى order.items

  • قيمة item.price

  • هل هي رقم أم نص؟

  • هل هناك عنصر ناقص؟


Debugging للـ DOM والواجهة

أحيانًا يكون الكود منطقيًا، لكن الواجهة لا تتغير. هنا لا يكفي فحص JavaScript فقط. قد تكون المشكلة في:

  • selector خاطئ

  • العنصر غير موجود بعد

  • الحدث لم يُربط أصلاً

  • CSS يخفي العنصر

  • تحديث الحالة يحدث لكن الrender لم يتم

  • DOM تغيّر بعد تحميل الصفحة

مثال شائع

document.querySelector("#saveBtn").addEventListener("click", function () {
  document.querySelector("#status").textContent = "Saved!";
});

إذا لم يعمل هذا، افحص:

console.log(document.querySelector("#saveBtn"));
console.log(document.querySelector("#status"));

إذا كانت النتيجة null، فالمشكلة ليست في addEventListener بل في العنصر نفسه أو توقيت تحميله.

مشكلة التوقيت

document.querySelector("#saveBtn").addEventListener("click", () => {
  console.log("Clicked");
});

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

document.addEventListener("DOMContentLoaded", () => {
  document.querySelector("#saveBtn").addEventListener("click", () => {
    console.log("Clicked");
  });
});

Debugging للأحداث Events

الأحداث في JavaScript تبدو سهلة، لكنها من أكثر مناطق الأخطاء شيوعًا. أحيانًا تربط الحدث بعنصر خطأ، أو تستخدم event delegation بشكل غير صحيح، أو تنسى أن الزر داخل form يسبب submit.

مثال على مشكلة submit

document.querySelector("button").addEventListener("click", () => {
  console.log("Button clicked");
});

إذا كان الزر داخل نموذج، فقد يحدث submit ويعيد تحميل الصفحة بسرعة فتظن أن الكود لم يعمل.

حل محتمل:

document.querySelector("form").addEventListener("submit", (e) => {
  e.preventDefault();
  console.log("Form submitted without reload");
});

جدول سريع لفهم مشاكل الأحداث

المشكلة

السبب المحتمل

طريقة الفحص

الحدث لا يعمل

العنصر غير موجود

فحص selector

يعمل ثم يختفي

إعادة تحميل الصفحة

فحص submit

يعمل على عنصر ويعطل على آخر

event delegation خطأ

فحص event.target

يحدث مرتين

listener مكرر

فحص التسجيل أكثر من مرة


Debugging في الـ Async JavaScript

الـ async code هو المكان الذي يضيع فيه الكثير من الوقت. لأن المشكلة لا تظهر دائمًا في السطر الذي تتوقعه، بل بعده بزمن.

المشهد الشائع

fetch("/api/user")
  .then((response) => response.json())
  .then((data) => {
    console.log(data.name);
  });

إذا لم يعمل، فالمشكلة قد تكون في:

  • الرابط نفسه

  • الاستجابة ليست JSON

  • الخادم يرجع خطأ

  • البيانات لا تحتوي name

  • then لا يحتوي return في مكان ما

مثال باستخدام async/await

async function loadUser() {
  try {
    const response = await fetch("/api/user");
    const data = await response.json();
    console.log(data.name);
  } catch (error) {
    console.error("Failed to load user:", error);
  }
}

هذا أفضل من التابع الطويل أحيانًا، لأنه يجعل المسار أوضح.

أين تضع breakpoint هنا؟

  • قبل fetch

  • بعد response

  • قبل response.json()

  • داخل catch

مشكلة شائعة جدًا: نسيان await

async function getData() {
  const data = fetch("/api/data");
  console.log(data);
}

هنا data ليست البيانات نفسها، بل Promise.

الصحيح:

async function getData() {
  const data = await fetch("/api/data");
  console.log(data);
}

جدول مختصر لفهم مشاكل async

العرض

السبب المحتمل

الحل

Promise { <pending> }

نسيت await

استخدم await أو then

خطأ بعد وقت

الاستدعاء متأخر

راقب stack trace

البيانات فارغة

response غير صحيح

افحص status

لا يوجد catch

لم تضف معالجة أخطاء

أضف try/catch


فحص الاستجابة من الـ API

عندما تستدعي API، لا تفترض أن كل شيء رجع كما تريد. افحص دائمًا:

  • response.ok

  • response.status

  • response.headers

  • صيغة JSON

  • البنية الفعلية للبيانات

مثال جيد

async function fetchUsers() {
  const response = await fetch("/api/users");

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  const data = await response.json();
  console.log(data);
}

بهذه الطريقة، لن تتعامل مع خطأ الشبكة والخطأ المنطقي كأنهما شيء واحد.


Debugging للـ Scope والنطاق

من أخطاء JavaScript المحبطة أن المتغير “يبدو موجودًا” في مكان، لكنه غير متاح في مكان آخر.

مثال

function test() {
  if (true) {
    let message = "Hello";
  }

  console.log(message);
}

هذا سيفشل لأن message محصورة داخل block.

جدول فهم النطاق

الكلمة المفتاحية

النطاق

ملاحظات

var

function scope

قد يسبب مشاكل قديمة

let

block scope

أفضل في أغلب الحالات

const

block scope

للثوابت المرجعية

مثال آخر على نطاق غير متوقع

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

هذا سيطبع:
0, 1, 2

لكن لو استعملت var قد تتغير النتيجة:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

هنا غالبًا ستحصل على 3 ثلاث مرات.


فهم this عندما يبدأ كل شيء بالارتباك

this من أشهر أسباب الضياع في JavaScript. لا تتعامل معه كمفهوم محفوظ، بل كقيمة تُحدد حسب طريقة الاستدعاء.

مثال بسيط

const user = {
  name: "Hassan",
  sayName() {
    console.log(this.name);
  },
};

user.sayName();

هنا this تشير إلى user.

لكن عند فصل الدالة:

const say = user.sayName;
say();

قد لا تكون this كما تتوقع.

كيف تصحح هذا؟

  • استخدم .bind()

  • استخدم arrow function في المواضع المناسبة

  • لا تفترض أن this ثابتة


كيف تكتشف المشاكل المنطقية

ليس كل bug خطأً في الكود. أحيانًا الكود صحيح نحويًا، لكنه خاطئ منطقيًا.

مثال

function isAdult(age) {
  return age > 18;
}

هذا قد يكون خطأ إذا كان تعريف البالغ يبدأ من 18 شاملًا. الصحيح:

function isAdult(age) {
  return age >= 18;
}

هذا النوع من الأخطاء لا يلتقطه JavaScript. لذلك تحتاج إلى اختبار منطقي.

اسأل نفسك دائمًا:

  • هل الشرط يعكس المطلوب فعلًا؟

  • هل الحدّ الأدنى أو الأعلى متضمن؟

  • هل المقارنة نصية أم رقمية؟

  • هل الفرع else منطقي فعلًا؟

  • هل القيمة الافتراضية مناسبة؟


Debugging أنواع البيانات

JavaScript أحيانًا تبتسم لك ثم تفاجئك بتحويلات غريبة.

مثال

console.log("5" + 1);

النتيجة:
"51"

لكن:

console.log("5" - 1);

النتيجة:
4

لماذا؟

لأن + قد يجري concatenation، بينما - يجبر التحويل إلى رقم.

جدول مهم جدًا

التعبير

النتيجة

السبب

"5" + 1

"51"

دمج نصوص

"5" - 1

4

تحويل رقمي

true + 1

2

true تصبح 1

null + 1

1

null تصبح 0

undefined + 1

NaN

قيمة غير رقمية

كيف تحمي نفسك؟

const value = Number(inputValue);
if (Number.isNaN(value)) {
  throw new Error("Invalid number");
}

Debugging الأداء Performance

أحيانًا الكود لا ينهار، لكنه بطيء. والبطء نفسه bug.

علامات مشكلة الأداء

  • الصفحة تتجمد

  • التمرير بطيء

  • النقر يتأخر

  • الذاكرة تزداد باستمرار

  • العمليات المتكررة تستهلك CPU

أسباب شائعة

السبب

المثال

التأثير

حلقة كبيرة

loop داخل loop

بطء ملحوظ

إعادة render متكرر

state updates كثيرة

واجهة بطيئة

DOM queries مكررة

querySelector داخل loop

استهلاك غير ضروري

listeners مكررة

event registration زائد

مشاكل ذاكرة

صور/بيانات ضخمة

تحميل غير محسوب

بطء في البداية

مثال غير جيد

for (let i = 0; i < 10000; i++) {
  const element = document.querySelector("#box");
  element.textContent = i;
}

هنا أنت تبحث عن العنصر 10000 مرة بلا داعٍ.

الأفضل:

const element = document.querySelector("#box");

for (let i = 0; i < 10000; i++) {
  element.textContent = i;
}

Debugging تسرب الذاكرة Memory Leaks

الذاكرة لا “تختفي” دائمًا بمجرد انتهاء استخدامك للكائنات. أحيانًا يبقى المرجع موجودًا، فيستمر المتصفح أو Node.js في الاحتفاظ بها.

أسباب شائعة

  • listeners لا تُزال

  • timers تبقى فعالة

  • arrays أو caches تكبر بلا حدود

  • closures تحتفظ ببيانات كبيرة

  • DOM elements محذوفة لكنها ما زالت مرجعية

مثال

let dataCache = [];

function addData(item) {
  dataCache.push(item);
}

إذا استمر dataCache في النمو دون تنظيف، فقد تتحول المشكلة إلى استهلاك ذاكرة حقيقي.

ماذا تفعل؟

  • راقب النمو

  • أزل listeners عند الحاجة

  • صفّر timers

  • نظّف المراجع غير الضرورية

  • استعمل WeakMap عندما يكون مناسبًا


Debugging في Node.js

المشكلة في Node.js أحيانًا تشبه المتصفح، لكن السياق مختلف. لا DOM هنا، لكن هناك:

  • ملفات

  • مسارات

  • بيئة

  • عمليات async

  • Streams

  • قواعد بيانات

  • أخطاء تشغيل

مثال على قراءة ملف

const fs = require("fs");

fs.readFile("data.json", "utf8", (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(JSON.parse(data));
});

إذا فشل هنا، افحص:

  • هل الملف موجود؟

  • هل المسار صحيح؟

  • هل JSON صالح؟

  • هل الصلاحيات مناسبة؟

أخطاء شائعة في Node.js

المشكلة

السبب المحتمل

طريقة التصحيح

MODULE_NOT_FOUND

مسار أو package ناقص

تحقق من require و npm install

EADDRINUSE

المنفذ مستخدم

غيّر المنفذ أو أوقف العملية

SyntaxError

صياغة خاطئة

راجع الملف

UnhandledPromiseRejection

Promise بدون catch

أضف معالجة أخطاء


أفضل طريقة لتصحيح الأخطاء في Promise chains

عندما تستخدم .then()، لا تنسَ أن تعيد القيم عند الحاجة.

مثال شائع على خطأ

fetch("/api/data")
  .then((response) => {
    response.json();
  })
  .then((data) => {
    console.log(data);
  });

هنا لم تُرجع response.json()، وبالتالي data قد تكون غير ما تتوقع.

الصحيح:

fetch("/api/data")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error(error);
  });

Debugging مع الاختبارات

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

لماذا الاختبارات مهمة في Debugging؟

  • تكشف الانكسار بسرعة

  • تعطيك مثالًا ثابتًا

  • تمنع إعادة ظهور الخطأ

  • تساعدك على فهم ما كان متوقعًا فعلًا

مثال Jest بسيط

function sum(a, b) {
  return a + b;
}

test("adds two numbers", () => {
  expect(sum(2, 3)).toBe(5);
});

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

استراتيجية عملية

  • ابدأ باختبار يفشل

  • أصلح السلوك

  • أعد تشغيل الاختبار

  • أضف حالات edge cases

  • لا تكتفِ بمسار النجاح فقط


Debugging حالات edge cases

أكثر الأخطاء إزعاجًا تحدث في الحالات النادرة:

  • مصفوفة فارغة

  • نص فارغ

  • قيمة null

  • رقم صفر

  • بيانات مكررة

  • زمن وصول غير متوقع

  • تحديث متزامن من أكثر من مكان

مثال

function firstItem(items) {
  return items[0];
}

هذا جيد حتى تأتي مصفوفة فارغة.

الأفضل:

function firstItem(items) {
  if (!Array.isArray(items) || items.length === 0) {
    return null;
  }

  return items[0];
}

جدول عملي: ماذا تفعل عندما يختفي الخطأ أو لا يظهر بوضوح؟

الحالة

الخطوة الأولى

الخطوة الثانية

الخطوة الثالثة

الخطأ متقطع

سجّل كل المدخلات

أعد الإنتاج عدة مرات

اعزل السبب

لا يوجد خطأ لكن النتيجة خاطئة

قارن المتوقع بالفعلي

افحص القيم خطوة خطوة

ضع breakpoint

المشكلة في الواجهة

فحص DOM وCSS

افحص الأحداث

راقب state

المشكلة في API

افحص network

راقب status

تحقق من body

المشكلة في الأداء

استخدم Performance

راقب loops

قلّل عمليات DOM


تقنية “divide and conquer”

عندما يصبح الكود كبيرًا، لا تحاول فهمه دفعة واحدة. قسّمه.

مثال

إذا كانت دالة كبيرة تفشل، اسأل:

  1. هل المشكلة قبل جلب البيانات؟

  2. أم أثناء المعالجة؟

  3. أم عند العرض؟

  4. أم بعد التفاعل مع المستخدم؟

ثم اختبر كل مرحلة وحدها.

function processProfile(profile) {
  const normalized = normalizeProfile(profile);
  const validated = validateProfile(normalized);
  return formatProfile(validated);
}

بدلًا من تصحيح الدالة كلها مرة واحدة، اختبر:

console.log(normalizeProfile(profile));
console.log(validateProfile(normalized));
console.log(formatProfile(validated));

أخطاء صغيرة تصنع فوضى كبيرة

هذه بعض الأخطاء التي تكرر نفسها كثيرًا:

1) نسيان return

function getNumber() {
  const n = 5;
}

2) المقارنة المربكة

if (value == false) {
  // ...
}

الأفضل غالبًا:

if (value === false) {
  // ...
}

أو حتى إعادة صياغة الشرط ليكون أوضح.

3) تغيير البيانات الأصلية دون قصد

const arr = [1, 2, 3];
const copy = arr;
copy.push(4);

الآن arr تغيرت أيضًا لأن copy تشير لنفس المرجع.

4) التعامل مع async كأنه sync

let result;
fetch("/api").then((r) => r.json()).then((data) => {
  result = data;
});
console.log(result);

غالبًا سيطبع undefined لأن التنفيذ لم ينتظر.


كيف تبني روتين Debugging احترافي؟

بدل التصرف بعشوائية، اجعل عندك خطوات ثابتة:

الروتين المقترح

  1. اقرأ الخطأ كاملًا.

  2. حدّد أين حدث.

  3. أعد إنتاجه.

  4. جرّد المشكلة إلى مثال صغير.

  5. راقب القيم.

  6. استخدم breakpoint أو console.

  7. صحح سبب المشكلة لا عرضها.

  8. اختبر بعد الإصلاح.

  9. أضف اختبارًا إن أمكن.

  10. تأكد أن الحل لم يكسر شيئًا آخر.


مثال كامل على Debugging من البداية للنهاية

المشكلة

زر الحفظ لا يحدّث الحالة.

الكود

let saved = false;

document.querySelector("#save").addEventListener("click", async () => {
  const response = fetch("/api/save");
  if (response.ok) {
    saved = true;
  }
});

ماذا نلاحظ؟

  • fetch لم يُنتظر

  • response هو Promise، وليس response فعليًا

  • لا يوجد await

  • response.ok لن يعمل كما يجب

الإصلاح

let saved = false;

document.querySelector("#save").addEventListener("click", async () => {
  try {
    const response = await fetch("/api/save");

    if (!response.ok) {
      throw new Error(`Save failed: ${response.status}`);
    }

    saved = true;
    console.log("Saved successfully");
  } catch (error) {
    console.error("Error saving:", error);
  }
});

لماذا هذا أفضل؟

  • أوضح

  • أكثر أمانًا

  • يلتقط الأخطاء

  • يعالج status

  • يسجل السبب الحقيقي عند الفشل


جدول مرجعي سريع للمحترف

المهمة

أفضل أداة

لماذا

معرفة القيمة الحالية

console.log

سريع وبسيط

تتبع الاستدعاء

console.trace

يظهر المسار

إيقاف التنفيذ

breakpoint

أدق من الطباعة

فحص الشبكة

Network tab

يكشف الطلبات

فحص الأداء

Performance

يكشف البطء

فحص الذاكرة

Memory tab

يكشف التسرب

قراءة بنية البيانات

console.table

واضح ومنظم

اختبار منطق معين

unit test

يمنع التكرار


نصائح ذهبية تقلل الأخطاء من الأصل

أحيانًا أفضل Debugging هو الذي لا تحتاجه كثيرًا. وهذه بعض العادات التي تقلل الفوضى:

  • اكتب أسماء متغيرات واضحة

  • لا تكرر المنطق في أكثر من مكان

  • اجعل الدوال صغيرة

  • افصل العرض عن المنطق

  • تحقق من المدخلات مبكرًا

  • لا تفترض نوع البيانات

  • استخدم linting

  • اكتب اختبارات للحالات الحساسة

  • راقب الأخطاء في الـ console باستمرار

  • نظّف console.log قبل النشر


متى تستخدم console.log ومتى لا تستخدمه؟

console.log مفيد، لكن ليس دائمًا أفضل اختيار.

استخدمه عندما:

  • تحتاج فحصًا سريعًا

  • تعمل على سلوك بسيط

  • تريد عرض قيمة مؤقتًا

لا تعتمد عليه وحده عندما:

  • المشكلة معقدة

  • هناك تداخل async

  • السلوك متكرر أو متشعب

  • تحتاج رؤية الـ call stack

  • تريد تحليل الأداء أو الذاكرة


مثال على Debugging أنيق مع Assertions

function multiplyPrice(price, quantity) {
  console.assert(typeof price === "number", "price must be a number");
  console.assert(typeof quantity === "number", "quantity must be a number");

  return price * quantity;
}

هذا لا يوقف التطبيق في كل الحالات، لكنه ينبهك عندما يكون هناك شيء غير صحيح.


كيف تعرف أن المشكلة انتهت فعلًا؟

لا تكتفِ بأن السطر “اشتغل مرة”. تأكد من الآتي:

  • هل المشكلة تعالجت في كل الحالات؟

  • هل اختفت الرسائل الخطأ؟

  • هل لا يزال الأداء جيدًا؟

  • هل لم يتغير سلوك آخر؟

  • هل الاختبار يمر؟

  • هل الكود واضح لشخص آخر؟

  • هل عالجت السبب أم العرض؟

المحترف لا يفرح فقط لأن الخطأ اختفى، بل لأنه فهم لماذا حدث.


خاتمة

تصحيح أخطاء JavaScript ليس مجرد مهارة تقنية، بل أسلوب تفكير. كل مرة تواجه فيها bug، أنت أمام فرصة لتقوية عينك البرمجية: أن ترى ما لا يُرى مباشرة، وأن تربط السطر بالنتيجة، والسبب بالأثر، والتوقيت بالسلوك.

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

الفرق الحقيقي ليس أن المحترف لا يخطئ. الفرق أنه يعرف أين ينظر، ومتى يتوقف عن التخمين، وكيف يحول الفوضى إلى خطوات واضحة.

#تصحيح أخطاء JavaScript #Debug JavaScript #تعلم JavaScript #Debugging JavaScript #أدوات المطور JavaScript #JavaScript Console #Chrome DevTools #إصلاح أخطاء جافاسكريبت #JavaScript للمبتدئين