الكود شبكة. لا شيء تقريبًا في الـ codebase يقف وحده: function تستدعي function أخرى، وقيمة تُضبط في مكان وتُقرأ في ثلاثة، ورقم يكتبه أحدهم في نموذج فيسافر عبر نصف دزينة من الملفات قبل أن يستقرّ في database ثم يعود ليظهر على شاشة. فهم أيّ قطعة بمفردها يعني فهم ما هي متّصلة به.
وهذا يمنحك سؤالين اثنين بالضبط، وهما السؤال نفسه موجّهًا في اتجاهين متعاكسين.
من أين جاء هذا؟ — إلى الوراء، نحو المصدر. أين يُستخدَم هذا؟ — إلى الأمام، نحو النتائج.
كلاهما كان مكلفًا فعلًا. كنت تفتح المشروع في محرّر، وتجري بحثًا نصّيًا، وتتبع الـ imports يدويًا، وتُبقي أربعة ملفات مفتوحة معًا ومكدّسًا ذهنيًا من هذا يُستدعى من هنا، وهذا يُستدعى من هنا، وأظنّ أن هذا يُستدعى من…. في codebase غير مألوف كانت ساعة كاملة قد تتبخّر في تتبّع واحد، ما يعني أن الناس كانوا يقنّنون فضولهم. يتتبّعون حين يضطرّون، ويخمّنون بقية الوقت.
هذه التكلفة صارت الآن جملة في المحادثة. ويستحقّ الأمر أن تتوقّف عند حجم هذا التغيير، لأن الاستجابة الصحيحة ليست أن تتتبّع أكثر قليلًا. بل أن تتتبّع باستمرار — حتى حين لا تحتاج إلى ذلك تمامًا — لأن ما جعل التتبّع مهارة تخصّصية لم يكن الصعوبة يومًا. كان السعر.
إلى الوراء: من أين جاء هذا؟
لنقل إنك تنظر إلى function اسمها saveMessage، ويُمرَّر إليها object اسمه msg. من أين جاء ذلك؟ لست بحاجة إلى التمرير، ولا إلى معرفة المشروع. تسأل:
من أين يأتي الـ msg الذي تستقبله saveMessage؟ تتبّعه إلى الوراء.
where does the msg that saveMessage receives come from? Trace it back.
ما يعود إليك ليس موقعًا، بل سلسلة — «يُبنى في handleContact داخل server/index.js من req.body: الاسم والبريد والرسالة التي كتبها أحدهم في contact.html، ثم يُمرَّر مباشرةً إلى saveMessage.»
هذه الجملة نموذج حقيقي لكيفية توصيل النظام، ولاحظ أنك ما كنت لتحصل عليها بالقراءة. إنها ليست مكتوبة في أيّ مكان. هي موجودة فقط كمسار عبر أربعة ملفات، وكان لا بدّ لشيء أن يمشي المسار ليصفه.
إلى الأمام: ما الذي يعتمد على هذا؟
اقلب الاتجاه الآن، لأن هذا هو الذي يتخطّاه الناس.
فهمت ما تفعله الـ function. السؤال التالي: من يعتمد عليها — وهذا هو السؤال الذي يخبرك ما إذا كان تغييرها آمنًا.
ما الذي يستدعي sendNotification؟ اعرض كل ملف وما يفعله بالنتيجة.
what calls sendNotification? List every file and what it does with the result.
جواب مثل «caller واحد — handleContact في server/index.js، يستدعيها مباشرةً بعد الحفظ ويتجاهل ما تُرجعه» يساوي أكثر ممّا يبدو. في سطر واحد صرت تعرف ما الذي سينكسر إن أخطأت، وما إذا كان بديلك ملزَمًا بإرجاع النوع نفسه من القيم، وأن البريد يُرسَل من مكان واحد بالضبط — فتغيير طريقة عمل البريد مهمّة في ملف واحد، لا رحلة استكشافية.
هذا هو السؤال الذي يطرحه المطوّرون الخبراء قبل كل تغيير ذي معنى، وهو الفرق بين «سأنقل هذه الـ function وحسب» و*«لماذا الـ production متوقّف؟»*. وهو أيضًا، وليس صدفةً، سؤال blast radius: أنت تحدّد إلى أيّ مدى يمكن أن تسافر نتائج تعديلٍ ما قبل أن تُجريه.
الحركة نفسها، بأربعة أشكال
متى أمسكت بالشكل، صارت التنويعات بديهية. كلها ابدأ من نقطة واحدة، واتبع السلك:
تتبّع البيانات إلى الوراء — من أين تأتي الرسائل في messages.json فعلًا؟ تتبّعها إلى الوراء حتى النموذج. / where do the messages in messages.json actually come from? Trace it back to the form.
تتبّع البيانات إلى الأمام — حين يرسل أحدهم نموذج التواصل، ماذا يحدث؟ اشرح لي كل function تعمل، بالترتيب. / when someone submits the contact form, what happens? Walk me through every function that runs, in order.
تتبّع الـ imports — ما الذي يستورده server/index.js، وماذا يفعل كلٌّ منها؟ / what does server/index.js pull in, and what does each one do?
اعثر على مكان الشيء — أين في هذا المشروع يُرسَل البريد فعلًا؟ / where in this project does the email actually get sent?
الأخيرة تستحقّ ملاحظة، لأنها التي تنقذ الناس. غالبًا لن يكون لديك رمز تبدأ منه — سيكون لديك سلوك. شيء يعرض تاريخًا خاطئًا، أو بريد يخرج باسم خاطئ، أو مجموع ينقصه سنت. يمكنك أن تتتبّع من وصفٍ تمامًا كما تتتبّع من اسم، ولمن لا يكتب الكود لكسب رزقه، هذا هو المكان الذي يبدأ منه التتبّع فعلًا في العادة.
اطلب القائمة، لا الجواب
هذه هي العادة الواحدة التي تستحقّ أن تأخذها من هذه الصفحة.
ستُغرى بأن تسأل «هل هذه الـ function مستخدَمة في أيّ مكان؟» ثم تتصرّف بناءً على «نعم». لا تفعل. اطلب دائمًا أن تُعرَض الـ callers ملفًا ملفًا، لسببين يتراكمان.
الأول أن «نعم» لا تخبرك شيئًا تقريبًا. أنت لم ترد أن تعرف هل هي مستخدَمة، بل أردت أن تعرف ماذا سيحدث لو غيّرتها، والقائمة وحدها تجيب عن ذلك.
والثاني هو المهمّ: القائمة قابلة للتحقّق، و«نعم» ليست كذلك. إن أُعطيت خمسة ملفات، يمكنك أن تفتح واحدًا وتتأكّد أنه يستدعي الشيء فعلًا. يستغرق ذلك خمس عشرة ثانية، وهو كامل الفرق بين تتبّع تعتمد عليه وتتبّع يبدو صحيحًا فحسب. Claude يقرأ ملفات حقيقية وهو مصيب في العادة — لكن «مصيب في العادة» صفة تريد أن تكون قادرًا على اختبارها بثمن بخس، والقائمة هي ما يجعل الاختبار بخسًا. «نعم» لا تعطيك شيئًا تشدّ عليه.
تتبّعٌ لا يمكنك التحقّق منه ليس تتبّعًا. إنه حكاية.
ولهذا أيضًا يحسّن طلب القائمة الجوابَ نفسه بهدوء، لا قدرتك على تدقيقه فقط. طلب تعداد كل caller سؤال أصعب من طلب تأكيد حدس، وهو ينتج قراءة أدقّ. تحصل على تتبّع أفضل وقابل للتحقّق من الجملة نفسها.
أين يؤتي هذا ثماره
التتبّع هو الحركة التي تحوّل codebase غير مألوف من جدار إلى خريطة، وهو يتراكم بطريقة يسهل الاستهانة بها: كل تتبّع تجريه يتركك تعرف أكثر قليلًا عن شكل النظام، فيبدأ التالي من أرض أعلى.
وهو أيضًا الحركة الأولى الصحيحة في الموقف الذي يدفع معظم الناس إلى طلب المساعدة أصلًا — أن ترث شيئًا لم يترك له أحد توثيقًا. لست بحاجة إلى أن يتذكّر أحد كيف يعمل. أنت بحاجة إلى نقطة تعرفها واتجاه تمشي فيه.
النصف الآخر من قراءة الكود هو ما تفعله حين يكون معطوبًا لا غير مألوف فحسب: جدار النصّ الأحمر، والـ stack trace، والخطأ الذي يسمّي ملفًا لم تسمع به قط. ذلك هو قراءة الأخطاء والـ stack traces، وهو الغريزة نفسها موجَّهة إلى مشكلة مختلفة.