برگردان غیردقیق بخشهایی از ویرایش دوم کتاب برنامهنویس عملگرا
دیدن خطای خود و دانستن اینکه هیچکس غیر از خودتان در ایجاد آن نقش نداشته، بسیار دردناک است.
Ajax, Sophocles
هیچکس برنامه بینقص نمینویسد و بیشتر وقت برنامهنویسان صرف دیباگ کردن میشود. در ادامه برخی از تکنیکهای عمومی برای پیدا کردن باگها را مطرح میکنیم.
دیباگ کردن کد برای بسیاری از توسعهدهندگان فرایندی حسی و حساس است. ممکن است بهجای اینکه با یک باگ بهعنوان مساله یا پازلی که باید حل شود برخورد کنید، در مقابل آن گارد بگیرید، بهانههای سطح پایین بیاورید، وجودش را انکار کنید، دیگران را مقصر بدانید یا حتی نسبت به آن بیعلاقگی نشان دهید.
بهتر است این حقیقت را بپذیرید که دیباگ کردن نوعی حل مساله است و تعامل خود با باگ را اینگونه پیش ببرید. اگر باگ شخص دیگری را پیدا کرده اید میتوانید انرژی و توان خود را صرف انتقاد از او کنید. در بعضی از محیطهای کاری این امر جزیی از فرهنگ تیم شده و توسعهدهندگان رفع باگ را بدون سرزنش دیگران انجام نمیدهند. بهعنوان برنامهنویس عملگرا بهتر است تمام وقت شما صرف حل مشکل شود نه سرزش دیگران.
مشکل را حل کنید و دست از سرزنش بردارید
مهم نیست که باگ حاصل کار شما یا شخص دیگری است. مساله حاضر، مساله شماست.
پیش از شروع فرایند دیباگ کردن بهتر است ذهن خود را آماده کنید. بسیاری از مسائل احساسی مانند حفظ غرور و فشار روانی باید کنار گذاشته شوند. اصل اول دیباگ کردن این است.
وحشت نکنید
ترسیدن بخصوص زمانی که ددلاینی نزدیک است، رییستان نگران است یا مشتری عصبانی است و شما باید علت باگ را پیدا کنید، بسیار ساده است اما نکته مهم این است که یک قدم عقب بایستید و به دنبال ریشه واقعی مشکل بگردید.
اگر اولین واکنشتان در مقابل گزارش یک باگ «غیر ممکن است» بود، بدانید که قطعا اشتباه میکنید. ذهن خود را با «اما این نمیتواند اتفاق بیفتد» خسته نکنید چرا که هر چیزی میتواند اتفاق بیفتد و اکنون هم اتفاق افتادهاست.
در زمان دیباگ کردن نزدیکبین نباشید. در مقابل وسوسه اینکه فقط نشانههای باگ را برطرف کنید، مقاومت کنید. به احتمال فراوان علت اصلی و ریشه مشکل چند گام عقبتر از محدوده دید شما قرار گرفته است. همیشه تلاش خود را معطوف به پیدا کردن ریشه مشکل کنید و به نشانههای ثانویه بسنده نکنید.
قبل از کار روی باگ، مطمئن شوید که نسخه نهایی و تمیز کد را در اختیار دارید. سطح هشدار کامپایلر خود را بالا ببرید و درگیر پیدا کردن خطاهایی که کامپیوتر میتواند پیدا کند، نشوید.
برای حل هر مسالهای ابتدا باید تا جای ممکن اطلاعات کسب کنید. متاسفانه گزارش دادن باگ علم نیست و همین باعث میشود بروز برخی همرویدادها به سادگی مسیر دیباگ کردن را منحرف کنند. هرگز وقت خود را برای دیباگ کردن همرویدادها تلف نکنید و پیش از شروع کار دقت خود را در مشاهدات بالا ببرید.
در برخی موارد گزارشدهنده باگ از فرایند فنی بازتولید آن بیاطلاع است. در این موارد سعی کنید عملکرد کاربر گزارشدهنده باگ را بررسی کنید و جزییات لازم در سطح مورد نظر خود را از زمان وقوع باگ کسب کنید.
زمانی که مطمئن شدید که مشکل را میدانید وقت آن است که ببینید برنامه چطور عمل میکند.
بهترین روش برای رفع یک باگ، پیدا کردن راه بازتولید آن است. اگر نتوانید باگ را بازتولید کنید چگونه میخواهید متوجه برطرف شدن آن بشوید؟
بازتولید یک باگ با انجام مراحل پیچیده و طولانی مطلوب ما نیست. هدف، بازتولید باگ تنها با یک دستور یا یک گام است. اگر برای بازتولید باگ نیاز به 15 گام مختلف باشد فرایند رفع آن بسیار پیچیده و دشوار میشود.
از مهمترین نکات دیباگ کردن این است:
ابتدا تست درست با نتیجه نادرست و سپس اصلاح کد
در بسیاری موارد با مجبور کردن خود به اینکه شرایط بازتولید باگ را محدود و محدودتر کنید، درک بهتری از نحوه رفع آن به دست میآورید. فرایند نوشتن تست راه حل را پیشنهاد میدهد.
استراتژی قبلی برای محدود کردن شرایط بسیار مفید است اما وقتی با 50 هزار خط کد روبهرو هستید باید چکار کنید؟ ابتدا به مشکل نگاه کنید. آیا اجرای برنامه متوقف شده؟ شگفتانگیز است که برخی برنامهنویسان بعد از دیدن پیغام خطا در کد به دنبال آن میگردند!
پیغام لعنتی را بخوانید
اگر برنامه متوقف نشده باشد؟ اگر پیغام نمایش داده شده اشتباه باشد؟ با دیباگر و تست خود به سراغ بازتولید مشکل بروید.
گاهی اوقات مساله ساده است: insert_rate برابر 4.5 است در حالی که باید 0.045 باشد. بیشتر اوقات برای پیدا کردن اینکه چرا این مقدار اشتباه است باید عمیقتر به کد نگاه کنید. مطمئن باشید که می توانید به راحتی در پشته فراخوانی (call stack) بالا و پایین بروید و فراخوانیها و مقادیر را بررسی کنید.
همراه داشتن خودکار و کاغذ و نوشتن نکات و نقاط شروع میتواند به بازگشت به نقطه قبلی و سردرگم نشدن کمک بزرگی کند. گاهی اوقات سرنخی را دنبال میکنیم که به نتیجهای نمیرسد. با نوشتن نقطه شروع و علت آن، میتوانیم بدون هدر رفت زمان به همان نقطه بازگردیم و مسیر دیگری را پیش بگیریم.
گاهی اوقات به پشته فراخوانی نگاه میکنید و خیال میکنید که پایانی برای آن وجود ندارد. در این مواقع از برش دودویی کمک بگیرید و در هر مرحله نیمی از پشته را دور بریزید تا به نقطه مورد نظر برسید.
هر فارغالتحصیل کامپیوتر حداقل یکبار مجبور شده الگتوریتم جستجوی دودویی را برای لیستهای مرتب پیادهسازی کند. ایده آن بسیار ساده و نتیجه آن بسیار سریع است. از ایده همین الگتوریم میتوان برای دیباگ کردن بهره برد.
وقتی با stack trace بسیار بزرگی سر و کار دارید و به دنبال آن هستید که متد دارای مشکل را پیدا کنید، پشته را به صورت تقریبی از میانه به دو قسمت تقسیم کنید و به دنبال مشکل بگردید. همین کار با نیمه اول یا دوم (به صورت بازگشتی) انجام دهید تا سرانجام به خطای مورد نظر برسید.
اگر خطا روی مجموعه خاصی از دادهها رخ میدهد میتوانید از همین ایده کمک بگیرید. مجموعه دادهها را کوچک و کوچکتر کنید تا دادهای که به مشکل ختم میشود را پیدا کنید.
اگر تیم شما در مجموعهای از نسخهها باگ جدیدی را بهوجود آورده نیز می توانید از همین روش استفاده کرده و با تست نسخههای مختلف، نسخهای که موجب بروز باگ شده است را پیدا کنید. استفاده از یک ابزار کنترل ورژن در این امر کمک بسیاری خواهد کرد.
ابزارهای دیباگر معمولا روی وضعیت فعلی برنامه متمرکز میشوند و دیدی نسبت به وضعیت پیشین آن ندارند. در برخی مواقع برای یافتن علت مشکل لازم است که وضعیت برنامه و دادهها در طول زمان مورد بررسی قرار گیرند. مشاهده stack trace مسیر رسیدن به وضعیت فعلی را مشخص میکند و بخصوص در سیستمهای مبتی بر رخداد، نمیتواند درباره زنجیره فراخوانیها اطلاعاتی در اختیار شما قرار دهد.
دنبال کردن مسیر برنامه تکنیک سادهایست که میتواند در سیستمهای همزمان، مبتنی بر رخداد یا بلادرنگ کمک بسیار زیادی به درک درست علت مشکل و حل آن کند. برای اینکار کافی است در نقاط مختلف برنامه پیامهای متناسبی قرار دهید و با بررسی آنها دید خود به مساله را عمیقتر کنید.
یک تکنیک بسیار ساده اما موثر برای پیدا کردن ریشه مشکل توضیح آن به دیگران است. شخص دیگر باید پشت سر شما بایستد، به مانیتورتان خیره شود و مدام سرش را تکان دهد (مثل اردک پلاستیکی در وان حمام که مدام بالا و پایین میرود) و لازم هم نیست چیزی بگوید. فرایند توضیح مشکل، گام به گام و آنچه که کد باید انجام دهد، معمولا باعث میشود مشکل از مانیتور بیرون بپرد و خودش را نشان دهد.
به نظر ساده میرسد اما در توضیح مساله به دیگران باید نکاتی که خودتان پیشفرض در نظر گرفتهاید را با جزییات زیاد مطرح کنید. همزمان با به زبان آوردن این پیشفرضها دید شما به مشکل نیز تغییر میکند. اگر شخص دیگری همراهتان نیست و به تنهایی کار میکنید، مشکل را برای اردک پلاستیکی یا گلدان خود شرح دهید.
در بیشتر پروژهها برنامهای که شما روی آن کار میکنید ترکیبی از کد تیم شما، برنامههای طرف سوم (پایگاه داده، اتصال، فریمورکهای وب، الگوریتمها و ...) و محیط توسعه (سیستم عامل، کامپایلر و ...) است.
ممکن است باگی در سطح سیستم عامل، کامپایلر و برنامه طرف سوم وجود داشته باشد اما این امر نباید اولین دیدگاه شما باشد. احتمال اینکه باگ در کد تیم شما وجود داشته باشد بسیار بالاتر است. اینکه برنامه شما از یک کتابخانه طرف سوم به درستی استفاده نمیکند باورپذیرتر از آن است که کتابخانه اشتباه کار میکند. حتی اگر کتابخانه دارای باگ باشد برای اطمینان ابتدا باید کد خود را از فرایند تست حذف کنید.
برنامهنویس ارشدی در پروژهای کار میکرد و پس از مدتی کلنجار رفتن با یک باگ عمیقا معتقد بود که select در سیستم Unix خراب است. هیچ منطق و فشاری هم نظرش را عوض نمیکرد. این حقیقت که همه برنامههای دیگر که از select استفاده میکنند بدون باگ هستند هم در نظرش اثری نداشت. او هفتهها صرف دور زدن مشکل و پیادهسازی روشهای عجیب برای برطرف کردن باگ کرد اما در نهایت مشکل پابرجا باقی ماند. سرانجام از طرف مدیران مجبور شد که داکیومنت select را مطالعه کند. پس از آن باگ مورد نظر را در چند دقیقه برطرف کرد.
همیشه در مواجهه با باگ در نظر داشته باشید که:
تابع select خراب نیست
به خاطر داشته باشید که اگر رد سُم دیدید ابتدا به اسب فکر کنید نه گورخر! سیستم عامل به احتمال زیاد خراب نیست و select هم به درستی کار می کند.
اگر شما «فقط یک چیز را عوض کردهاید» و سیستم خراب شده است، به احتمال زیاد همان یک چیز به صورت مستقیم یا غیرمستقیم (هر چقدر دور) مسئول این خرابی است. گاهی اوقات تغییرات بیرون از محدوده پروژه شما میتوانند روی پروژه شما تاثیر بگذارند. ورژنهای جدید سیستمعامل، کامپایلر، پایگاه داده و ... میتوانند باعث به وجود آمدن باگهای جدید در کد شما شوند. در زمان وقوع این مدل تغییرات کل سیستم باید از ابتدا مورد تست قرار گیرد.
وقتی از وقوع یک باگ غافلگیر میشوید (شاید زیر لب بگویید «محال است») باید حقایقی را که نزد خود ثابت کردهاید زیر سوال ببرید. در محاسبه تخفیف (همان که فکر میکردید در برابر هر باگی مقاوم است و امکان بروز باگ در الگوریتمش وجود ندارد) آیا همه محدودیتها را بررسی کرده بودید؟ آن قطعه کد که سالهاست استفاده میکنید نمیتواند باعث بروز باگ شود؟
البته که میشود. مقدار غافلگیری در زمان بروز خطا با میزان اعتماد و باوری که به کد خود دارید متناسب است. به همین دلیل، در زمان مواجهه با یک باگ غافلگیر کننده باید به این نتیجه برسید که یکی از فرضیات شما اشتباه بوده است.
از بررسی یک قطعه کد فقط به این دلیل که «میدانید» درست کار میکند چشمپوشی نکنید. ثابت کنید. در زمینه موجود، با دادههای و محدودیتهای موجود درست بودن آن را ثابت کنید.
فرض نکنید، ثابت کنید.
در مواجهه با یک باگ غافلگیر کننده، همزمان با رفع باگ این سوال را مطرح کنید که چرا این باگ پیش از این کشف نشده است. روش تست خود را بررسی کنید و در صورت نیاز تغییرش دهید.
اگر باگ غافلگیر کنندهای در سیستم رخ داد به این نکته فکر کنید که آیا جای دیگری از سیستم حاوی کدهایی هست که با فرض درست بودن استفاده شدهاند؟ آیا وقتش نشده که درست بودنشان ثابت شود؟
اگر رفع یک باگ زمان زیادی از شما گرفته، چرایی آن را از خود بپرسید. آیا راه بهتری برای رفع این مدل باگها در آینده وجود دارد؟ نوشتن هوکهای جدید تست یا لاگهای جزئیتر کمکی میکند؟
اگر باگ حاصل فرض اشتباه شخص دیگری در تیم است، مشکل را با تیم مطرح کنید. اگر یک نفر اشتباه متوجه شده باشد، امکان اینکه دیگران نیز اشتباه متوجه شوند وجود دارد.