اسکریپت یک لوازمالتحریری در تبریز جمع فروش روز را چاپ میکند و عدد ۷۴۰۰۰۰ میدهد. جمع درست ۹۵۰۰۰۰ است. هیچ خطایی، هیچ متن قرمزی و هیچ چیزی برای جستوجو کردن در کار نیست، و همین حالت است که به روش نیاز دارد.
روش با ایستادن داخل تابع شروع میشود، وقتی هنوز مقدارها دستش است:
python3 -m pdb -c "break jam_rooz" -c continue -c "p radifha" forooshgah.py- چاپ میکند
[('1042', 3), ('۱۰۴۳', 5), ('1042', 1)]
یک دستور دیگر تکلیف را روشن میکند: p [k in NERKHHA for k, _ in radifha] جواب [True, False, True] میدهد. کد وسطی با ارقام فارسی تایپ شده است. رشته «۱۰۴۳» با رشته لاتین برابر نیست، پس جستوجو در جدول نرخ رد میشود، get صفر برمیگرداند، و آن سطر رایگان فروخته میشود. ۲۱۰۰۰۰ تومان همانجا گم شد.
بدجنسی ماجرا این است که هیچ اعتبارسنجیای جلویش را نمیگیرد: int("۱۲۳") در پایتون عدد ۱۲۳ میدهد و هیچ خطایی نمیدهد، و "۱۰۴۳".isdigit() هم درست است. از دید هر بررسیای، آن کد یک عدد سالم است.
پنج ایستگاه، و اولی همان است که رد میشود
اول بازتولیدش کنید، وگرنه هیچ چیز بعدش قابل اندازهگیری نیست. خرابیای که «گاهی» اتفاق میافتد هنوز یک اشکال نیست، یک شایعه است، و هر ویرایشی که علیه شایعه انجام شود چیزی را که دارید نگاه میکنید عوض میکند. اینجا یک دستور بود و هر بار همان عدد اشتباه، که ارزانترین جای ممکن برای کار کردن است.
بعد جواب درست را بنویسید. ۹۵۰۰۰۰، یعنی سه دفتر و پنج خودکار و یک دفتر دیگر با نرخهای جدول. بدون آن عدد راهی نیست که بفهمید کی درست شده، و مقدار قابل توجهی از اشکالزدایی، دو نفرند که سر اینکه برنامه قرار بود چه چاپ کند اختلاف دارند.
بعد ورودی را کوچک کنید. سه سطر دو سطر شد و اشتباه ماند، که گفت اشکال در جمع زدن است نه در خواندن فایل و نه در مرتب کردن. نصف کردن ورودی کل تکنیک است، و همان حرکتی است که روی تاریخچه هم جواب میدهد: git bisect کامیتی را پیدا میکند که جواب در آن عوض شده، و همان پرسشی را درباره ده کامیت میپرسد که وگرنه باید درباره هزارتا میپرسیدید.
ایستاندن برنامه، بهجای اضافه کردن چاپ به آن
breakpoint() در خود زبان است و PYTHONBREAKPOINT=0 همهشان را خاموش میکند، و تفاوتش با همان دستورهای چاپی که جایشان را گرفت همین است: یک نقطه توقف فراموششده را میشود از محیط خاموش کرد و یک چاپ فراموششده را باید پیدا و پاک کرد. سطر قبل از دردسر بگذاریدش و اسکریپت را عادی اجرا کنید.
داخلش سه دستور بیشتر کار را انجام میدهند. p radifha داخل pdb آرگومانی را چاپ میکند که تابع با آن صدا زده شده. w داخل pdb پشته را با یک فلش روی قابی که در آن هستید چاپ میکند، پس زنجیره از ماژول تا خود جمع یک کلید است. و u یک قاب بالا میرود، جایی که سطرها از آن آمدهاند، که معمولاً اشتباه واقعی همانجاست. همین ایستاندن برنامه و نگاه کردن به دور و برش است، و ردپای خطایی که وقتی برنامه واقعاً میشکند شما را به آنجا میرساند سطری که اهمیت دارد بهندرت سطر اول است.
وقتی خرابی جلوی چشم شما نیست
دیباگر لازم دارد خرابی وقتی اتفاق بیفتد که شما تماشا میکنید، و این اسکریپت شب اجرا میشود. لاگ برای همین است و چاپ با تشریفات اضافه نیست. logging.basicConfig(level=logging.DEBUG) سطرهای دیباگ را بدون ویرایششان روشن میکند، و بردن سطح روی WARNING دوباره خاموششان میکند، پس همان سطرها برای خرابی بعدی در فایل میمانند بهجای اینکه دو بار نوشته شوند.
جوری بنویسیدشان که مقدارها زنده بمانند: log.debug("kod %s peyda nashod", kod) قالببندی را به خود logger میسپارد، یعنی وقتی سطح خاموش است آن سطر هزینهای ندارد و آرگومانها هنوز برای هر چیزی که خروجی را دادهای میخواند سر جایشاناند. اینکه چه چیزی ثبت شود تا خرابی فردا قابل تشخیص باشد چه چیزی ثبت کنید تا خرابی بعدی قابل تشخیص باشد است، و نسخه کوتاهش این است: ورودی و تصمیم، هرگز نتیجه.
لایهها
هر بررسی چه چیزی را میگیرد، و چه چیزی را رد میکند
چهار چیز میان آن کد فارسی و جمع نهایی روز ایستاده بودند، و هر چهارتا ردش کردند.
- بررسیکننده نوع+رشتهای که جایش عدد بود→رشتهای که عدد به نظر میرسد و با کلید جدول برابر نیست
- بازرس کد+شاخه بیاستفاده، نام پوشاندهشده، ایمپورتی که کسی به کار نمیبرد→هر اشکالی که املایش درست باشد
- آزمونها+هر چیزی که کسی فکرش را کرده و ادعایش را نوشته→حالتی که کسی تصورش را نکرده، که همیشه همین یکی است
- بازبینی کد+کدی که غلط خوانده میشود→کدی که خوب خوانده میشود و چیز اشتباهی حساب میکند
نوشتن فقط ستون چپ، این شکل را به آگهی چهار ابزار تبدیل میکرد. ستون راست دلیل وجود آن روش بالاست، و دلیل اینکه آخرین لایه، حسابداری است که میداند آن روز چقدر فروخته.
ابزار، و سالی که این حرف درست است
breakpoint() در پایتون 3.7 در سال ۲۰۱۸ آمد و جای تایپ کردن import pdb; pdb.set_trace() را گرفت، که دلیل حضور آن سطر در هر جواب نوشتهشده پیش از آن سال است. رایگان است و در خود زبان.
ماژول logging در کتابخانه استاندارد است، رایگان، و جای دستورهای چاپی را گرفت که بعد کامیت میشدند؛ شهرت بدقلقیاش فقط مربوط به پیکربندیاش است و دو سطر بالا تمام چیزی است که یک اسکریپت لازم دارد. git bisect هم رایگان است و از سال ۲۰۰۵ در Git هست، و جای خواندن لاگ و حدس زدن اینکه کدام تغییر این کار را کرده گرفت. یک کامیت سالم و یک کامیت خراب به آن بدهید و در حدود ده گام، روی هزار کامیت، شما را سر مقصر میگذارد. هر سه بدون شبکه کار میکنند، که برای این یکی موضوع نیست چون هر سه از قبل روی ماشین شما هستند.
جایی که این درس در ایران میایستد: داده فارسی است و ابزارها نیستند
اشتباه رایج، ویرایش کردن پیش از بازتولید است و همهجا هم هست. اما چیزی که اینجا فرق میکند این است که یک خانواده کامل از اشکالها از هر چهار لایه محافظ رد میشوند، چون هر چهار لایه برای داده لاتین ساخته شدهاند. ارقام فارسی که عدد سالم به حساب میآیند، حرفهای عربی که با فارسی یکسان دیده میشوند و برابر نیستند، تاریخ شمسی که بهشکل رشته مرتب میشود، و متن راستبهچپ که وسط یک گزارش بریده میشود. هیچکدام نحو را نمیشکنند، هیچکدام آزمون را قرمز نمیکنند، و همهشان سر خواننده درمیآیند.
نتیجه عملیاش این است که در یک کد فارسی، ایستگاه دوم گرانتر و لازمتر است. عدد درست را باید از جایی بیرون از برنامه گرفت، از یک فاکتور کاغذی یا از حسابدار، چون خود داده همان چیزی است که مشکوک است. و وقتی درست شد، همان سطر عجیب را بهشکل یک آزمون نگه دارید؛ سطری با کد فارسی حالا یک آزمون است تا نفر بعدی مجبور نباشد این را دو بار پیدا کند.
قدیمیترین تکنیک این صفحه هیچ ابزاری ندارد: بلند توضیح دادن خرابی، به ترتیب، برای کسی که هیچ تصوری از موضوع ندارد. آنقدر جواب میدهد که خجالتآور باشد، و دلیلش این است که توضیح دادن، ترتیب را به کلمه تبدیل میکند و شکاف معمولاً در گامی است که بیصدا از رویش میپریدید. این همان چرا بلند گفتن مسئله حلش میکند است.
چیزی که هیچکدام از اینها جواب نمیدهد این است که آن کد فارسی از کجا وارد برنامه شد، که پرسشی درباره مرز ورود داده است و به آزمون و انتشار وصل میشود، زیر انتشار و استقرار. کدی که از اول خوانا باشد هر جلسه بالا را کوتاه میکند، که کد تمیز نوشتن است، و قابهایی که دیباگر رویشان راه میرود کد چطور اجرا میشود، هر دو زیر ایدههایی که با عوض شدن زبان از بین نمیروند.