Python Learningاز صفر تا نوشتن پروژه از حفظ
درس ۱۶ · استادی

باگ‌گیری

حدود ۶۰ دقیقه۸ تمرین۶ سؤال آزمون
بعد از این درس می‌توانی:
  • یک روش قدم‌به‌قدم برای پیدا کردن باگ داشته باشی
  • با print و repr و type ببینی داخل برنامه چه می‌گذرد
  • باگ‌های واقعی همین پروژه را بشناسی و درستشان کنی
  • فرق «خطا» (برنامه می‌ایستد) و «باگ منطقی» (اشتباه کار می‌کند) را بدانی

روش شش‌قدمی

  1. بازسازی: دقیق بنویس با چه کاری باگ دیده می‌شود. اگر نمی‌توانی تکرارش کنی، نمی‌توانی درستش کنی.
  2. پیام خطا را بخوان: آخرین خط (نوع و دلیل) و شماره‌ی خطی که به کد خودت اشاره می‌کند.
  3. حدس بزن: کدام متغیر احتمالاً مقدار اشتباه دارد؟
  4. نگاه کن: با print(repr(x), type(x)) درست قبل از خط مشکوک مقدار واقعی را ببین.
  5. یک تغییر: فقط یک چیز را عوض کن و دوباره امتحان کن.
  6. تست: بعد از درست شدن، حالت‌های دیگر را هم امتحان کن که چیز دیگری خراب نشده باشد.
اجرا کن

خودت امتحان کن

Ctrl + Enter
نکته

اردک پلاستیکی: کد را خط به خط برای یک نفر (یا یک اردک!) توضیح بده. خیلی وقت‌ها وسط توضیح خودت باگ را پیدا می‌کنی.

شکار باگ

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

تمرین

باگ ۱: جمع متن

قیمت کل باید ۷۵۰۰ باشد، ولی چیز عجیبی درمی‌آید.

Ctrl + Enter
راهنمایی

print(repr(price)) را امتحان کن. '2500' * 3 متن را سه بار تکرار می‌کند؛ ولی '2500' * '3' اصلاً خطا می‌دهد.

دیدن راه‌حل (اول خودت تلاش کن)
price = '2500'
qty = '3'
total = float(price) * int(qty)
تمرین

باگ ۲: همه‌ی دکمه‌ها گزارش می‌دهند

هر «دکمه» باید اسم کار خودش را برگرداند، ولی همه report برمی‌گردانند.

Ctrl + Enter
راهنمایی

lambda مقدار action را هنگام صدا زدن می‌خواند. با پارامتر پیش‌فرض مقدار همان لحظه را نگه دار.

دیدن راه‌حل (اول خودت تلاش کن)
actions = ['add', 'edit', 'report']
commands = []
for action in actions:
    commands.append(lambda a=action: a)
results = [c() for c in commands]
تمرین

باگ ۳: nan رد نمی‌شود

تابع باید nan را رد کند، ولی نمی‌کند.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
def valid(buy, sell, qty):
    return all(0 <= x < float('inf') for x in (buy, sell, qty))
تمرین

باگ ۴: موجودی کم نمی‌شود

بعد از فروش، موجودی در products باید ۱ شود ولی ۳ می‌ماند.

Ctrl + Enter
راهنمایی

dict(...) یک کپی می‌سازد. ارجاع به همان دیکشنری لازم است.

دیدن راه‌حل (اول خودت تلاش کن)
products = {'pen': {'qty': 3}}
product = products['pen']
product['qty'] -= 2
تمرین

باگ ۵: کرش با فایل قدیمی

فایل‌های قدیمی کلید sales ندارند و برنامه با KeyError بسته می‌شود.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
data = {'products': {}}
products = data.get('products', {})
sales = data.get('sales', [])
تمرین

باگ ۶: جست‌وجو اثر ندارد

این شبیه‌سازی run است. بعد از جست‌وجوی pen جدول باید فقط pen داشته باشد، ولی همه را دارد.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
products = ['pen', 'cup', 'pencil']
table = []

def refresh(query=''):
    table.clear()
    table.extend(p for p in products if query in p)

def search():
    refresh('pen')

def run(action):
    action()
    if action is not search: refresh()

run(search)
تمرین

باگ ۷: None به‌جای جعبه

این شبیه‌سازی Entry است. entry.get() خطای AttributeError: 'NoneType'... می‌دهد.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
class Entry:
    def __init__(self):
        self.text = 'pen'
    def grid(self):
        return None
    def get(self):
        return self.text

entry = Entry()
entry.grid()
name = entry.get()
تمرین

باگ ۸: فروش صفر عدد

فروش صفر عدد نباید مجاز باشد، ولی هست.

Ctrl + Enter
دیدن راه‌حل (اول خودت تلاش کن)
def check_sell(qty, stock):
    if qty <= 0:
        raise ValueError('تعداد باید مثبت باشد')
    if qty > stock:
        raise ValueError('موجودی کافی نیست')
    return True

باگ‌های واقعی همین پروژه

باگنشانهدرمانخط
جست‌وجو بی‌اثردکمه‌ی جست‌وجو همه‌ی کالاها را نشان می‌دادif action is not search: refresh()خط ۸۷ پروژه
nan و infقیمت nan ثبت و NaN در JSON ذخیره می‌شدall(0 <= x < float('inf') ...)خط ۳۱ پروژه
فایل غیر دیکشنریبرنامه با AttributeError باز نمی‌شدگرفتن AttributeErrorخط ۲۵ پروژه
مسیر نسبیاجرا از پوشه‌ی دیگر = داده‌ی خالیمسیر از __file__خط ۶ پروژه
نکته

بعد از هر باگی که پیدا می‌کنی، یک تست کوچک برایش بنویس (مثل تمرین‌های این سایت) تا دوباره برنگردد.

آزمون این درس

برای گذراندن این درس حداقل ۸۰٪ جواب درست لازم است. هر چند بار خواستی می‌توانی دوباره امتحان بدهی.

۱. اولین قدم برای درست کردن یک باگ؟
۲. print(repr(x)) چه فایده‌ای دارد؟
۳. '2500' * 3 چیست؟
۴. AttributeError: 'NoneType' object has no attribute 'get' معمولاً یعنی
۵. فرق «خطا» با «باگ منطقی»؟
۶. بعد از درست کردن باگ چه کنی؟