راهنمای کامل دیباگ کردن: روشی که وقت شما را نمی‌خورد

راهنمای کامل دیباگ کردن: روشی که وقت شما را نمی‌خورد
در این مقاله می‌خوانید
  1. قاعدهٔ صفر: حدس نزنید
  2. گام اول: بازتولید کنید
  3. گام دوم: دامنه را نصف کنید
  4. همین کار روی تاریخچهٔ گیت
  5. گام سوم: مشاهده کنید، نه اینکه فرض کنید
  6. console — سریع ولی محدود
  7. Breakpoint — وقتی باید حالت را کاوش کنید
  8. تب Network — برای وقتی که مشکل از داده است
  9. گام چهارم: علت را پیدا کنید، نه علامت را
  10. Stack trace را چطور بخوانیم
  11. دیباگ بر اساس نوع باگ
  12. باگی که گاهی رخ می‌دهد
  13. باگی که فقط برای بعضی کاربران است
  14. باگی که بعد از انتشار شروع شد
  15. باگی که هیچ خطایی نمی‌دهد
  16. ضدالگوها
  17. وقتی گیر کردید
  18. وقتی باگ فقط در production است
  19. جمع‌بندی
  20. منابع و مطالعهٔ بیشتر

دیباگ کردن مهارتی است که تقریباً هیچ‌کس رسماً یادش نگرفته. بیشتر ما با آزمون و خطا به روشی شخصی رسیده‌ایم که گاهی جواب می‌دهد و گاهی چند ساعت را می‌سوزاند. تفاوت بین توسعه‌دهنده‌ای که باگ را در بیست دقیقه پیدا می‌کند و کسی که نصف روز درگیرش است، معمولاً هوش نیست — روش است.

این راهنما همان روش است.

قاعدهٔ صفر: حدس نزنید

رایج‌ترین اشتباه در دیباگ این است که به محض دیدن خطا، حدسی می‌زنیم و شروع می‌کنیم به تغییر دادن کد تا ببینیم درست می‌شود. این کار گاهی جواب می‌دهد و همان «گاهی» است که آن را خطرناک می‌کند: یاد می‌گیریم روش درستی است.

مشکل حدس زدن سه چیز است. اول اینکه اگر باگ حل شد، نمی‌دانید چرا حل شد. دوم اینکه در مسیر، تغییرات دیگری هم داده‌اید که ممکن است باگ جدید ساخته باشند. سوم اینکه اگر حل نشد، حالا وضعیت اولیه را هم از دست داده‌اید.

روش جایگزین ساده است: هر بار یک فرضیه، یک تغییر، یک نتیجه.

گام اول: بازتولید کنید

پیش از هر کاری باید بتوانید باگ را عمداً ایجاد کنید. اگر نمی‌توانید، هر «رفعی» که انجام دهید حدس است — چون راهی برای تأیید ندارید.

برای بازتولید، این چهار چیز را مشخص کنید:

  • ورودی: دقیقاً چه داده‌ای؟ کاربر لاگین‌کرده یا مهمان؟ سبد خالی یا پر؟
  • محیط: کدام مرورگر، کدام دستگاه، کدام اندازهٔ صفحه؟
  • مسیر: کاربر پیش از این لحظه کجا بود و چه کرد؟
  • تکرارپذیری: هر بار رخ می‌دهد یا گاهی؟

اگر پاسخ آخری «گاهی» است، احتمالاً با مسئلهٔ زمان‌بندی روبه‌رو هستید و باید دنبال عملیات ناهم‌زمانی بگردید که ترتیبشان تضمین‌شده نیست.

وقتی باگ در محیط production رخ می‌دهد و شما نمی‌توانید بازتولیدش کنید، معمولاً یکی از این چهار مورد را نمی‌دانید. اینجاست که داشتن مسیر کاربر و اطلاعات مرورگر در لحظهٔ خطا — چیزی که سیستم‌های رصد خطا به‌صورت breadcrumb ذخیره می‌کنند — تفاوت بین حل شدن و نشدن است. اینکه چرا چنین باگ‌هایی اصلاً فقط آنجا دیده می‌شوند را در چرا باگ‌ها فقط در محیط production ظاهر می‌شوند توضیح داده‌ایم.

گام دوم: دامنه را نصف کنید

قوی‌ترین تکنیک دیباگ، جستجوی دودویی است. به‌جای اینکه از اول کد شروع کنید و جلو بروید، وسط را نگاه کنید:

آیا داده در نقطهٔ وسط درست است؟ اگر بله، مشکل در نیمهٔ دوم است. اگر نه، در نیمهٔ اول. تکرار کنید. هر مرحله فضای جستجو را نصف می‌کند — یعنی در یک مسیر ده مرحله‌ای، سه چهار بررسی کافی است.

همین کار روی تاریخچهٔ گیت

اگر می‌دانید قبلاً کار می‌کرده و حالا نه، لازم نیست کامیت‌ها را دستی بگردید. دستور git bisect دقیقاً همین جستجوی دودویی را روی تاریخچه انجام می‌دهد:

git bisect start
git bisect bad                # نسخهٔ فعلی خراب است
git bisect good v1.4.0        # این نسخه سالم بود
# گیت هر بار کامیتی وسط را می‌آورد؛ تست کنید و بگویید:
git bisect good   یا   git bisect bad

در پروژه‌ای با هزار کامیت، حدود ده بار تست کافی است تا کامیت مقصر پیدا شود.

گام سوم: مشاهده کنید، نه اینکه فرض کنید

در این مرحله باید ببینید داده در هر نقطه واقعاً چیست. سه ابزار دارید و هرکدام جای خودش را دارد.

console — سریع ولی محدود

برای نگاه سریع خوب است، ولی اگر فقط console.log بلدید، بخش زیادی را از دست می‌دهید:

console.table(users);            // آرایهٔ اشیاء را جدولی نشان می‌دهد
console.group('checkout');       // گروه‌بندی خروجی
console.time('fetch');           // اندازه‌گیری زمان
console.trace();                 // نشان می‌دهد این خط از کجا صدا زده شده

Breakpoint — وقتی باید حالت را کاوش کنید

مزیت اصلی breakpoint نسبت به لاگ این است که همهٔ متغیرها را در آن لحظه در دسترس دارید، نه فقط آن‌هایی که از قبل حدس زده بودید مهم‌اند.

دو نوع کمتر شناخته‌شده که وقت زیادی صرفه‌جویی می‌کنند: breakpoint شرطی که فقط وقتی شرطی برقرار باشد متوقف می‌شود (برای حلقه‌ای که در تکرار ۴۷۳ خراب می‌شود)، و breakpoint روی تغییر DOM که وقتی عنصری تغییر می‌کند متوقف می‌شود — بهترین راه برای فهمیدن اینکه چه کدی دارد صفحه را دستکاری می‌کند.

تب Network — برای وقتی که مشکل از داده است

بخش بزرگی از باگ‌هایی که «فرانت‌اند» به نظر می‌رسند، در واقع پاسخ غیرمنتظرهٔ سرورند. پیش از گشتن در کد، ببینید سرور واقعاً چه فرستاده است. این تب و ده‌ها قابلیت کمتر شناخته‌شدهٔ دیگر را در Chrome DevTools: بیست قابلیتی که احتمالاً نمی‌شناسید مرور کرده‌ایم.

گام چهارم: علت را پیدا کنید، نه علامت را

وقتی جایی رسیدید که مقدار غلط است، وسوسه می‌شوید همان‌جا اصلاحش کنید. گاهی درست است، ولی معمولاً شما علامت را پیدا کرده‌اید نه علت.

سؤالی که باید بپرسید: چرا این مقدار اینجا غلط است؟ و بعد همان سؤال را دربارهٔ پاسخش. معمولاً بعد از سه چهار بار «چرا»، به جای واقعی می‌رسید.

مثال: دکمهٔ ثبت کار نمی‌کند، چون user.id تعریف‌نشده است. چرا؟ چون user هنوز از سرور نیامده. چرا کد پیش از رسیدنش اجرا شده؟ چون حالت بارگذاری در نظر گرفته نشده. این علت است. اصلاح در سطح اول (یک شرط دور user.id) باگ را پنهان می‌کند و همان مشکل جای دیگری دوباره ظاهر می‌شود. این همان الگوی خطای Cannot read properties of undefined است.

Stack trace را چطور بخوانیم

مهارتی که خیلی‌ها هرگز یاد نگرفته‌اند و بیشترین صرفه‌جویی زمانی را دارد.

stack trace فهرست فراخوانی‌هاست، از جدیدترین به قدیمی‌ترین. خط اول جایی است که خطا رخ داد؛ خطوط بعدی مسیری است که به آنجا رسید:

TypeError: Cannot read properties of undefined
    at renderProfile (profile.js:42:18)      ← اینجا رخ داد
    at UserPage (page.js:15:7)               ← این صدایش زد
    at renderApp (app.js:88:3)               ← و این آن را

سه قاعدهٔ عملی:

  • اولین خطی که به کد خودتان اشاره می‌کند را پیدا کنید. اگر ده خط اول مربوط به کتابخانه‌هاست، آن‌ها را رد کنید — خطا معمولاً از دادهٔ اشتباهی است که شما به کتابخانه داده‌اید.
  • خط بالا «کجا»ست، خطوط پایین «چرا». محل خطا را از خط اول بگیرید، ولی برای فهمیدن اینکه چرا داده اشتباه بود، مسیر را دنبال کنید.
  • اگر همه‌چیز مثل a.js:1:24817 است، source map ندارید. تا این را حل نکنید، هیچ خطای production قابل ردیابی نیست.

دیباگ بر اساس نوع باگ

روش کلی بالا برای همه‌چیز کار می‌کند، ولی هر خانواده از باگ‌ها میان‌بُر خودش را دارد. انواع باگ را در باگ نرم‌افزاری چیست؟ دسته‌بندی کرده‌ایم.

باگی که گاهی رخ می‌دهد

تقریباً همیشه مسئلهٔ زمان‌بندی است. دنبال عملیات ناهم‌زمانی بگردید که ترتیبشان تضمین نشده — دو fetch هم‌زمان، یا تایمری که با رندر رقابت می‌کند. برای بازتولید، سرعت شبکه را در ابزار توسعه کند کنید؛ این کار شرایط مسابقه را پررنگ می‌کند.

باگی که فقط برای بعضی کاربران است

دنبال وجه اشتراک بگردید، نه دنبال کد. همه‌شان یک مرورگر دارند؟ همه‌شان کاربر جدیدند؟ همه‌شان روی موبایل‌اند؟ وقتی الگو را پیدا کردید، معمولاً خودِ الگو جواب را می‌گوید.

باگی که بعد از انتشار شروع شد

سراغ کد نروید؛ سراغ تفاوت بروید. git bisect یا حتی نگاه کردن به فهرست تغییرات آن انتشار، سریع‌تر جواب می‌دهد.

باگی که هیچ خطایی نمی‌دهد

سخت‌ترین نوع. اینجا کنسول کمکی نمی‌کند و باید مقدارها را در طول مسیر دنبال کنید تا جایی که برای اولین بار غلط می‌شود. جستجوی دودویی دقیقاً برای همین حالت ساخته شده.

ضدالگوها

  • تغییر چند چیز هم‌زمان. اگر درست شد، نمی‌دانید کدام‌یک کار کرد.
  • «شاید کش است» بدون بررسی. گاهی هست، ولی این جمله معمولاً بهانه‌ای برای فکر نکردن است.
  • اضافه کردن try/catch برای ساکت کردن خطا. این رفع باگ نیست؛ پنهان کردن آن است و بعداً به شکل بدتری برمی‌گردد.
  • ادامه دادن وقتی خسته‌اید. دیباگ کار شناختی سنگینی است. بارها دیده شده که مسئله‌ای پس از چند ساعت تلاش، صبح روز بعد در ده دقیقه حل می‌شود.
  • توضیح ندادن به کسی. تکنیک «اردک پلاستیکی» — توضیح دادن مسئله با صدای بلند برای یک شیء بی‌جان — واقعاً جواب می‌دهد، چون توضیح دادن شما را مجبور می‌کند فرضیاتتان را صریح کنید و معمولاً همان‌جاست که اشتباه را می‌بینید.

وقتی گیر کردید

گاهی روش هم جواب نمی‌دهد و ساعت‌هاست روی یک باگ مانده‌اید. چند حرکت که معمولاً قفل را باز می‌کنند:

فرضیات را بنویسید. روی کاغذ فهرست کنید چه چیزهایی را «مطمئن» هستید. بعد یکی‌یکی واقعاً بررسی‌شان کنید. باگ تقریباً همیشه در یکی از همان مواردی است که مطمئن بودید درست است.

برعکس بروید. به‌جای اینکه از ورودی جلو بیایید، از خروجی غلط عقب برگردید و بپرسید این مقدار از کجا آمد.

ساده‌اش کنید تا بشکند. کد را آن‌قدر کم کنید تا کوچک‌ترین نمونه‌ای بماند که هنوز باگ را دارد. معمولاً وسط این کار، خودِ مشکل آشکار می‌شود.

بپذیرید که شاید باگ جای دیگری است. اگر ساعت‌ها در یک فایل گشته‌اید و چیزی پیدا نکرده‌اید، احتمالاً درست می‌گویید که آنجا مشکلی نیست.

بلند شوید. جدی‌ترین توصیهٔ این فهرست. دیباگ کار شناختی سنگینی است و ذهن خسته در حلقه گیر می‌کند — همان مسیر را بارها می‌رود. بیست دقیقه فاصله معمولاً از دو ساعت اصرار مؤثرتر است.

وقتی باگ فقط در production است

دشوارترین حالت، و متأسفانه رایج‌ترین. چند نکتهٔ عملی:

Source map تولید کنید. بدون آن، stack trace کد فشرده چیزی مثل a.js:1:24817 است و عملاً بی‌فایده.

لاگ اضافه کردن به production آخرین راه است، نه اولین. هر انتشار جدید چرخه‌ای چند ساعته است. پیش از آن، از داده‌ای که از قبل دارید حداکثر استفاده را ببرید.

زمینه را از قبل جمع کنید. بهترین کار این است که پیش از وقوع باگ، سیستمی داشته باشید که خطا را همراه مرورگر، آدرس صفحه، مسیر کاربر و — اگر خودش گزارش داده — اسکرین‌شات ثبت کند. آن‌وقت دیباگ کردن باگ production شبیه دیباگ کردن روی دستگاه خودتان می‌شود. روش‌های بی‌خطرش را در دیباگ در production بدون خراب کردن چیزی مفصل آورده‌ایم.

جمع‌بندی

دیباگ خوب کار کارآگاهی است، نه شانس: بازتولید کنید، دامنه را نصف کنید، مشاهده کنید به‌جای فرض کردن، و تا رسیدن به علت واقعی «چرا» بپرسید. همین چهار قدم، بیشتر جلسات چندساعتهٔ دیباگ را به کار بیست دقیقه‌ای تبدیل می‌کند.

و مهم‌تر از همه: بیشتر وقتِ دیباگ صرف پیدا کردن باگ می‌شود، نه رفع آن. هر کاری که مرحلهٔ پیدا کردن را کوتاه کند — از یک اسکرین‌شات ساده تا ثبت خودکار مسیر کاربر — ارزشش را چند برابر پس می‌دهد.

منابع و مطالعهٔ بیشتر

باگ‌ماگ را رایگان امتحان کنید

خطاهای سایتتان را خودکار ثبت کنید و بگذارید کاربران با یک کلیک باگ گزارش دهند.

شروع رایگان