کوروش فرجی
9 یادداشت منتشر شدهچطور باگ ها را سریع تر پیدا کنیم؟ روش سیستماتیک دیباگ، از فرضیه تا رفع ریشه ای
همه ما این صحنه را تجربه کرده ایم: برنامه ای که دیروز درست کار می کرد، امروز ناگهان خطا می دهد، یا خروجی عجیبی تولید می کند. اولین واکنش معمولا این است که چند خط کد را حدسی عوض کنیم و دوباره اجرا بگیریم؛ اگر درست شد، خب شد، اگر نشد، چند خط دیگر را عوض کنیم. این روش گاهی جواب می دهد، اما معمولا وقت زیادی می گیرد و بدتر از آن، علت اصلی را پنهان می کند. باگی که با حدس رفع شده باشد، خیلی وقت ها چند هفته بعد، در شرایط دیگری، دوباره برمی گردد.
دیباگ خوب، بیشتر شبیه کار یک کارآگاه است تا شانس آوردن. یعنی به جای اینکه بپرسیم «کجای کد خراب است؟»، قدم به قدم شواهد جمع می کنیم تا محدوده مشکوک کوچک و کوچک تر شود. در ادامه، یک روش ساده و عملی را مرور می کنیم که هم برای تمرین های دانشگاهی جواب می دهد و هم برای پروژه های واقعی.
۱. اول باگ را بازتولید کنید
پیش از هر تغییری، باید بتوانید باگ را با یک ورودی مشخص، به صورت پایدار تکرار کنید. اگر باگ فقط «گاهی» رخ می دهد، هنوز برای رفع آن آماده نیستید؛ چون نمی توانید مطمئن شوید واقعا رفع شده است. ورودی، مراحل انجام کار، پیام خطا و خروجی مورد انتظار را بنویسید. همین یادداشت کوتاه، بعدا در مرحله آزمون نهایی هم به کارتان می آید.
۲. کوچک ترین مثال ممکن را بسازید
اگر برنامه بزرگ است، سعی کنید باگ را در یک فایل کوچک و مستقل تکرار کنید. داده های اضافی، بخش های نامربوط و کدهای حاشیه ای را حذف کنید تا فقط همان بخشی بماند که مشکل در آن رخ می دهد. هرچه مثال کوچک تر باشد، پیدا کردن علت راحت تر است. این کار گاهی خودش علت را نشان می دهد؛ چون در مسیر حذف بخش های اضافی، متوجه می شوید کدام بخش، رفتار را تغییر می دهد.
۳. یک فرضیه بنویسید و فقط همان را بیازمایید
دیباگ سیستماتیک، مثل روش علمی است: مشاهده می کنیم، فرضیه می سازیم، پیش بینی قابل آزمون می کنیم و بعد آزمایش می کنیم. مثلا به جای اینکه بگویید «احتمالا این تابع خراب است»، بگویید: «اگر مقدار این متغیر در این خط، صفر باشد، خروجی باید چنین شود؛ پس مقدارش را چاپ یا بررسی می کنم.» قانون طلایی اینجاست: در هر آزمایش، فقط یک چیز را تغییر دهید. تغییر هم زمان چند بخش، حتی اگر مشکل حل شود، نمی گذارد بفهمید کدام تغییر واقعا موثر بوده است.
۴. با جست وجوی دودویی، محدوده را نصف کنید
به جای خواندن خط به خط کل کد، هر بار نیمی از محدوده مشکوک را حذف کنید. اگر باگ در نیمه اول نیست، پس در نیمه دوم است و برعکس. در پروژه هایی که با گیت نسخه بندی می شوند، ابزار git bisect همین کار را روی تاریخچه کد انجام می دهد: یک نسخه سالم و یک نسخه خراب را مشخص می کنید و ابزار با آزمایش نسخه های میانی، دقیقا همان تغییری را پیدا می کند که باگ را وارد کرده است. با git bisect run حتی می توان این آزمایش را خودکار کرد.
۵. کد را با زبان ساده توضیح دهید؛ روش اردک لاستیکی
یکی از قدیمی ترین و موثرترین تکنیک ها، توضیح خط به خط کد با زبان طبیعی است؛ همان روشی که به «اردک لاستیکی» معروف شده و ریشه اش در کتاب The Pragmatic Programmer است. کد را جمله به جمله توضیح دهید که «قرار است چه کند» و «در عمل چه می کند». اختلاف همین دو جمله، معمولا محل فرض اشتباه، شرط مرزی، مقدار پیش فرض نادرست یا برداشت غلط از یک تابع است. لازم نیست حتما اردک داشته باشید؛ توضیح دادن برای یک هم تیمی، یا حتی نوشتن توضیح در یک برگه، همان اثر را دارد.
۶. دیباگر برای لحظه وقوع، لاگ برای روند در طول زمان
دیباگر و لاگ، دو ابزار مکمل اند، نه رقیب. وقتی می خواهید مقدار واقعی متغیرها و مسیر فراخوانی تابع ها را در لحظه خطا ببینید، نقطه توقف (breakpoint) بگذارید و با گام های Step Into و Step Over جلو بروید و پنجره Call Stack را بررسی کنید. اما برای باگ هایی که دیر رخ می دهند، متناوب اند یا فقط در محیط واقعی دیده می شوند، لاگ سطح بندی شده بهتر است: DEBUG برای جزئیات تشخیص مشکل، INFO برای تایید کارکرد مورد انتظار، WARNING برای رویداد غیرمنتظره و ERROR برای ناتوانی در انجام یک کارکرد. به جای پخش کردن print در سراسر کد، از لاگر استفاده کنید و در پایان کار، خروجی های موقت را پاک کنید.
چند اشتباه رایج که باید از آن ها دوری کنید
- نادیده گرفتن پیام خطا و ردیابی پشته (stack trace)؛ معمولا دقیق ترین سرنخ همان جاست.
- اصلاح نشانه به جای علت ریشه ای؛ مثلا پنهان کردن خطا با try/except خالی، مشکل را حل نمی کند، فقط دیرتر نشانش می دهد.
- رها کردن آزمون بازتولید پس از اصلاح؛ بعد از رفع باگ، همان مثال اولیه را دوباره اجرا کنید و مطمئن شوید هم باگ رفع شده و هم چیز دیگری خراب نشده است (رگرسیون).
یک چک لیست کوتاه برای دفعه بعد
بازتولید کردم؟ کوچک ترین مثال را ساختم؟ یک فرضیه مشخص نوشتم؟ فقط یک تغییر را آزمایدم؟ نتیجه را ثبت کردم؟ با دیباگر یا لاگ، شواهد دیدم؟ و در پایان، آزمون بازتولید را دوباره اجرا کردم؟ اگر جواب بیشتر این سوال ها «بله» باشد، معمولا باگ سریع تر — و ماندگارتر — حل می شود.
دیباگ، مهارتی است که با تمرین، سریع تر و آرام تر می شود. هدف این نیست که هرگز باگ نداشته باشیم؛ هدف این است که وقتی باگ آمد، به جای حدس و اضطراب، یک مسیر مشخص برای پیدا کردن و رفع ریشه ای آن داشته باشیم.
