کد چگونه اجرا می شود
کدی که می نویسید فقط متن است و پردازنده متن نمی خواند؛ قبل از هر اتفاقی باید به شکلی ترجمه شود که ماشین بتواند اجرایش کند. زبان ها در اینکه این ترجمه کی انجام می شود و نتیجه اش کجا نگه داشته می شود با هم فرق دارند، و بیشتر خطاهای گیج کننده دقیقا سر همین تفاوت پیش می آیند.
- درس ۲ از ۱۴
- مقدماتی
- رایگان، بدون ثبت نام
زنجیره ای که هر خط کد از آن رد می شود
-
۱
متنی که نوشتید
یک فایل متنی معمولی؛ از نظر دیسک با یک یادداشت فرقی ندارد.
-
۲
بررسی نحو
روی کل فایل و قبل از هر اجرایی. اینجا که بشکند، هیچ خطی اجرا نمی شود.
-
۳
ترجمه به شکل قابل اجرا
بایت کد، آپکد یا کد ماشین. گاهی روی دیسک می ماند، گاهی در حافظه، گاهی هیچ جا.
-
۴
اجرا روی پردازنده
تنها جایی که واقعا کاری انجام می شود، و تنها جایی که خطای زمان اجرا در آن پیدا می شود.
این زنجیره ساده شده است. در عمل بین دو حلقه آخر لایه های دیگری هم هستند، از سیستم عامل تا بهینه سازی هایی که موتور زبان حین اجرا انجام می دهد.
آخرین بررسی: فکت ها و نام ابزارهای این درس در همین تاریخ با منابعشان بازبینی شده اند.
کامپیوتر متن کد را نمی خواند؛ پس چه چیزی را اجرا می کند؟
فایلی که ذخیره می کنید یک متن ساده است، از نظر دیسک تفاوتی با یک فایل یادداشت ندارد. پردازنده این متن را نمی خواند. چیزی که پردازنده اجرا می کند مجموعه ای از دستورهای عددی بسیار ساده است: این عدد را از حافظه بردار، با آن یکی جمعش کن، نتیجه را آنجا بگذار، و اگر صفر شد به فلان نقطه بپر. به این می گویند زبان ماشین و هیچ کس امروز مستقیم با آن کار نمی کند.
پس بین متن شما و پردازنده همیشه یک مرحله ترجمه هست. سه حلقه دارد و ترتیبش هم عوض نمی شود: متنی که نوشتید، برنامه ای که آن متن را می خواند و بررسی و ترجمه می کند، و شکل ترجمه شده ای که در نهایت اجرا می شود. هیچ زبانی از این زنجیره فرار نمی کند. زبان ها فقط سر دو چیز با هم فرق دارند: ترجمه کی انجام می شود، و نتیجه اش نگه داشته می شود یا نه.
یک نکته که خیلی زود به کارتان می آید: مرحله ترجمه قبل از هر چیز نحو را بررسی می کند، یعنی اینکه متن شما اصلا قواعد آن زبان را رعایت کرده یا نه. اگر نکرده باشد کار همان جا متوقف می شود و هیچ خطی از برنامه اجرا نمی شود، حتی خطی که سالم بود و قبل از اشتباه نوشته شده بود. بخش چهارم همین صفحه این را با دو فایل واقعی نشان می دهد.
کامپایل و تفسیر چه فرقی دارند؟
ساده ترین شکلش این است: در زبان تفسیری متن همان طور که هست به موتور زبان داده می شود و از بالا به پایین اجرا می شود؛ در زبان کامپایلی متن اول به شکل دیگری تبدیل می شود و بعد آن شکل اجرا می شود. توضیح خود موزیلا هم دقیقا همین است، که کد در زبان تفسیری در همان شکل متنی دریافت و از روی همان پردازش می شود، و در زبان کامپایلی قبل از اجرا به شکل دیگری تبدیل می شود.
حالا حقیقت ناخوشایند. این تقسیم بندی امروز خط کشی تمیزی نیست. موزیلا در همان صفحه اضافه می کند که بیشتر مفسرهای مدرن جاوااسکریپت از تکنیکی به نام کامپایل درجا استفاده می کنند و کد را حین اجرا به یک شکل باینری سریع تر تبدیل می کنند؛ و با این حال جاوااسکریپت را همچنان تفسیری می شمارد، چون آن کامپایل در زمان اجرا انجام می شود نه از قبل.
پس سوال «کامپایلی است یا تفسیری» کم کم دارد بی فایده می شود. سوال مفیدتر دو تاست: ترجمه کی انجام می شود، و نتیجه اش نگه داشته می شود یا هر بار از نو ساخته می شود؟ جواب همین دو سوال است که توضیح می دهد چرا یک برنامه سی قبل از اجرا یک مرحله ساخت جدا دارد، و چرا یک اسکریپت پایتون که ماژول وارد می کند بار دوم کمی سریع تر بالا می آید.
دو زمان بندی برای یک کار
هر دو ترجمه می کنند. تفاوت در این است که ترجمه کی انجام می شود، نه در اینکه ترجمه ای هست یا نه.
ترجمه قبل از اجرا
- یک مرحله ساخت جدا دارد که خودتان اجرایش می کنید
- خروجی یک فایل اجرایی است که روی دیسک می ماند
- خیلی از خطاها قبل از اولین اجرا پیدا می شوند
- عوض کردن فایل تا وقتی دوباره نسازید اثری ندارد
ترجمه هنگام اجرا
- مرحله جدایی ندارد؛ فایل را می دهید و اجرا می شود
- نتیجه ترجمه ممکن است در حافظه یا روی دیسک نگه داشته شود
- خیلی از خطاها تا لحظه رسیدن به آن خط پیدا نمی شوند
- فایل را عوض می کنید و دفعه بعد اثرش را می بینید
این دو ستون امروز کاملا از هم جدا نیستند: بیشتر موتورهای مدرن هر دو کار را با هم انجام می دهند و همین است که برچسب گذاری را بی فایده کرده.
پایتون، جاوااسکریپت و پی اچ پی هر کدام کجای زنجیره اند؟
جدول زیر همان دو سوال بخش قبل را روی چهار زبان می گذارد. ستون سوم مهم ترین ستون است، چون تفاوت واقعی زبان ها آنجاست نه در برچسبشان.
| زبان | چه چیزی ترجمه اش می کند | نتیجه ترجمه چیست | کجا اجرا می شود |
|---|---|---|---|
| پایتون | مفسر پایتون | بایت کد؛ برای ماژول ها در پوشه __pycache__ روی دیسک می ماند | روی همان کامپیوتر یا سرور |
| جاوااسکریپت | موتور مرورگر، یا Node روی سرور | کد ماشین که حین اجرا ساخته می شود و روی دیسک نمی ماند | مرورگر کاربر، یا سرور |
| پی اچ پی | موتور PHP | آپکد؛ با opcache در حافظه سرور می ماند، نه روی دیسک | سرور |
| سی | کامپایلر، در یک مرحله جدا قبل از اجرا | یک فایل اجرایی روی دیسک | مستقیم روی سیستم عامل |
سه سطر اول را کنار هم بگذارید و یک الگو پیدا می شود: هر سه ترجمه را در زمان اجرا انجام می دهند و تفاوتشان فقط در این است که نتیجه را کجا می گذارند. پایتون روی دیسک، پی اچ پی در حافظه، جاوااسکریپت هیچ جا. سطر چهارم عمدا اینجاست تا حالت واقعا متفاوت را ببینید: در سی اگر برنامه را کامپایل نکنید، هیچ چیزی برای اجرا وجود ندارد.
یک نتیجه عملی که همین جا به دردتان می خورد: در جاوااسکریپت و پی اچ پی و پایتون، فایل را عوض می کنید و دفعه بعد اثرش را می بینید. در سی باید دوباره بسازید. اگر کسی به شما گفت «فایل را عوض کردم ولی چیزی عوض نشد»، اولین سوال این است که آن زبان در کدام سطر این جدول است، و دومین سوال موضوع بخش پنجم است.
چرا گاهی هیچ خطی اجرا نمی شود و گاهی نصف برنامه؟
دو فایل کوچک بسازید و هر کدام را جدا اجرا کنید. اولی bad.py، با یک پرانتز بسته نشده در خط دوم:
print("first line")
print("second line"خروجی واقعی این است:
File "bad.py", line 2
print("second line"
^
SyntaxError: '(' was never closedدومی boom.py، که نحوش درست است ولی کاری می خواهد که شدنی نیست:
print("first line")
print(1/0)و خروجی اش:
first line
Traceback (most recent call last):
File "boom.py", line 2, in <module>
print(1/0)
~^~
ZeroDivisionError: division by zeroتفاوت را ببینید و همین یک چیز نصف دیباگ کردن شماست. در فایل اول جمله first line هرگز چاپ نشد، با اینکه خط اول کاملا درست بود و قبل از اشتباه نوشته شده بود. در فایل دوم چاپ شد و بعد برنامه وسط کار افتاد. دلیلش همان زنجیره بخش اول است: بررسی نحو روی کل فایل و پیش از هر اجرایی انجام می شود، پس یک پرانتز بسته نشده در خط دوم خط اول را هم از اجرا می اندازد. اما تقسیم بر صفر تا لحظه ای که واقعا به آن نرسیم کشف نمی شود، چون تا آن لحظه هیچ کس نمی داند مخرج صفر است.
این دو حالت اسم دارند: خطای نحوی و خطای زمان اجرا. و فایده عملی شان این است که از روی خروجی می شود تشخیصشان داد، حتی اگر زبان را بلد نباشید. اگر قبل از پیام خطا چیزی چاپ شده، برنامه شروع به اجرا کرده بود و شما با خطای زمان اجرا طرفید؛ اگر هیچ چیز چاپ نشده، به احتمال زیاد اصلا اجرا نشده است.
یک سوال که نوع خطا را مشخص می کند
قبل از پیام خطا، برنامه چیزی چاپ کرده بود؟
خطای نحوی
- برنامه اصلا شروع نشده؛ بررسی نحو قبل از اجرا شکسته است
- شماره خط در پیام معمولا درست است یا یکی دو خط پایین تر
- دنبال پرانتز، گیومه یا دو نقطه جا افتاده بگردید
خطای زمان اجرا
- برنامه اجرا شده و وسط کار به چیزی خورده که شدنی نبود
- آخرین خط پیام نوع خطا را می گوید و مهم ترین خط است
- مقدارها را همان جا چاپ کنید تا ببینید کدامشان آن چیزی نیست که فکر می کردید
این سوال نوع خطا را می گوید، نه علتش را. برنامه ای که بدون هیچ خطایی جواب اشتباه می دهد در هیچ کدام از این دو شاخه نمی افتد.
فایلی که ذخیره کردید همانی نیست که اجرا می شود
حالا جای جالب زنجیره. نتیجه ترجمه فقط در حافظه نمی ماند؛ خیلی وقت ها نگه داشته می شود تا دفعه بعد لازم نباشد دوباره ساخته شود. پایتون این کار را جلوی چشم شما انجام می دهد. دو فایل بسازید، یکی tools.py و یکی main.py که اولی را وارد می کند، و بعد:
python3 main.py
ls __pycache__/یک پوشه به اسم __pycache__ پیدا می شود و داخلش دقیقا یک فایل: tools.cpython-312.pyc. برای main.py هیچ فایلی ساخته نشده. مستند خود پایتون هر دو نکته را توضیح می دهد: پایتون نسخه کامپایل شده هر ماژول را در پوشه __pycache__ ذخیره می کند، و برای ماژولی که مستقیم از خط فرمان اجرا می شود همیشه دوباره کامپایل می کند و نتیجه را نگه نمی دارد.
پی اچ پی همین کار را می کند با یک تفاوت: نتیجه را روی دیسک نمی گذارد، در حافظه سرور نگه می دارد. اسم این لایه opcache است و رفتارش یک تنظیم دارد که تازه کارها را دیوانه می کند: هر چند وقت یک بار بررسی کند که آیا فایل روی دیسک عوض شده است. مستند خود پی اچ پی این تنظیم را با همین عبارت توضیح می دهد، هر چند وقت یک بار زمان تغییر فایل ها بررسی شود، برحسب ثانیه. یعنی بین ذخیره کردن فایل و اثر کردنش یک فاصله وجود دارد و آن فاصله عمدی است.
پس آن جمله همیشگی که «فایل را عوض کردم، هیچ اتفاقی نیفتاد» سه جواب رایج دارد و هر سه در همین زنجیره اند: یا فایلی که عوض کردید اصلا فایلی نیست که اجرا می شود، یا نسخه ترجمه شده قدیمی هنوز در حافظه است، یا خروجی از یک لایه کش جلوتر می آید و اصلا به برنامه شما نرسیده. بند «از تجربه خود ما» در پایین همین صفحه عدد این تنظیم را روی سرور خودمان می آورد.
لایه هایی که بین متن شما و فلز نشسته اند
-
۱
پردازنده
تنها چیزی که واقعا اجرا می کند، و فقط دستورهای عددی ساده می فهمد.
-
۲
سیستم عامل
حافظه، فایل و وقت پردازنده را بین برنامه ها تقسیم می کند.
-
۳
موتور زبان
مفسر پایتون، موتور جاوااسکریپت مرورگر، یا موتور پی اچ پی روی سرور.
-
۴
شکل ترجمه شده
بایت کد، آپکد یا کد ماشین؛ چیزی که واقعا خوانده و اجرا می شود.
-
۵
فایل متنی شما
بیرونی ترین لایه، و تنها لایه ای که مستقیم عوضش می کنید.
شماره ها ترتیب فاصله اند نه ترتیب اهمیت. برای برنامه نویسی روزمره لازم نیست هیچ کدام از این لایه ها را بلد باشید؛ لازم است بدانید وجود دارند.
وقتی برنامه کار نکرد، اول کجا را نگاه کنید؟
پیام خطا را از پایین بخوانید نه از بالا. آخرین خط نوع خطا را می گوید و همان مهم ترین کلمه صفحه است؛ خط بالای آن معمولا می گوید کجا. در نمونه بخش چهارم آخرین خط ZeroDivisionError: division by zero بود و خط بالایش دقیقا همان عبارتی را نشان می داد که ترکید. تازه کارها معمولا از بالا شروع می کنند، جایی که فقط مسیر فایل ها نوشته شده، و بعد می گویند خطا نامفهوم است.
بعد از خواندن، اسم لایه را بگویید. چهار تا بیشتر نیست. نحو: هیچ چیز اجرا نشد، پیام قبل از هر خروجی آمد. زمان اجرا: چیزی چاپ شد و بعد افتاد. محیط: برنامه سالم است ولی چیزی که لازم دارد نصب نیست، نسخه اش فرق دارد، یا مسیرش پیدا نمی شود. منطق: هیچ خطایی نیست، برنامه تا آخر اجرا می شود و جواب اشتباه می دهد.
موضع ما اینجاست و ساده هم هست: قبل از اینکه یک کاراکتر عوض کنید، اسم لایه را بلند بگویید. بیشتر ساعت هایی که تازه کارها هدر می دهند صرف تغییر دادن کد در لایه ای می شود که مشکل اصلا آنجا نیست، مثل بازنویسی یک تابع سالم وقتی کتابخانه اش نصب نبوده. لایه چهارم از همه بدتر است، چون هیچ خطایی نمی دهد؛ تنها راه گرفتنش این است که قبل از اجرا حدس بزنید خروجی چه باید باشد و بعد با چیزی که آمد مقایسه کنید.
مسیر سریع با هوش مصنوعی
کاری که مدل زبانی برای یک تازه کار واقعا سریع تر کرده خواندن پیام خطاست، ولی فقط اگر ترتیب را رعایت کنید. اشتباه رایج این است که آدم خودش حدس می زند مشکل کجاست و همان حدس را می پرسد؛ آن وقت جوابی می گیرد که درباره حدس اوست، نه درباره خطا. نسخه پایین ترتیب را برعکس می کند: اول لایه، بعد توضیح، آخر تغییر. برای این کار یک مدل قوی بهتر جواب می دهد چون کار تشخیص است؛ انتخاب فعلی ما در بخش هوش مصنوعی همین سایت هست.
- متن کامل خطا را کپی کنید، از اولین خط تا آخرین. نه عکس صفحه، نه خلاصه، نه فقط خط آخر؛ خط های بالایی جای وقوع را دارند.
- قبل از هر توضیحی، فقط بخواهید لایه را نام ببرد: نحو، زمان اجرا، محیط، یا منطق. یک کلمه کافی است و همین یک کلمه مسیر بعدی را تعیین می کند.
- بخواهید آن خط از کد شما را که خطا به آن اشاره می کند عینا نقل کند. اگر نتوانست، یعنی هنوز کد شما را ندارد و هر چه بگوید حدس است.
- حالا توضیح و کوچک ترین تغییر ممکن را بخواهید، نه بازنویسی فایل. یک بازنویسی کامل خطا را پنهان می کند و شما هرگز نمی فهمید چه بود.
- تغییر را خودتان اعمال کنید و اجرا کنید، و اگر باز خطا داد از قدم یک شروع کنید. اگر لایه محیط بود، نسخه زبان و سیستم عاملتان را هم در پیام بعدی بنویسید.
نسخه آماده کپی
این خطا را گرفتم. قبل از هر توضیحی، فقط به سه سوال زیر به همین ترتیب جواب بده.
۱. این خطا در کدام لایه است؟ فقط یکی را انتخاب کن: نحو، زمان اجرا، محیط، منطق.
۲. کدام خط از کد من باعثش شده؟ آن خط را عینا نقل کن. اگر کد را نداری، بگو چه چیزی لازم داری.
۳. کوچک ترین تغییری که این را درست می کند چیست؟ فقط همان تغییر، بدون بازنویسی بقیه فایل.
متن کامل خطا:
{اینجا بچسبانید، از اولین خط تا آخر}
کدی که اجرا کردم:
{اینجا بچسبانید}
محیط: {نسخه زبان} روی {سیستم عامل}
قبل از اعتماد به خروجی: لایه محیط را مدل از همه بدتر حدس می زند، چون کامپیوتر شما را نمی بیند: نسخه زبان، سیستم عامل و چیزهایی که نصب کرده اید را نمی داند و اگر ندهید جایش را با یک حدس پر می کند. پس هر مسیر فایل یا نام دستوری که پیشنهاد داد، قبل از اجرا با یک بررسی ساده تایید کنید. و اگر جواب قدم یک با چیزی که خودتان از خروجی دیدید نخواند، به خروجی اعتماد کنید نه به جواب.
هوش مصنوعی در این کار
یک عادت بیشتر از هر چیز دیگری سرعت یک تازه کار را بالا می برد: به جای جستجوی متن خطا در انجمن ها، متن کامل خطا را به یک مدل بدهید و بخواهید توضیحش بدهد. کاری که قبلا یک ساعت گشتن لازم داشت حالا یک پیام است. موضع ما این است که همین کار هم ترتیب دارد: اول لایه، بعد توضیح؛ برعکسش وقت تلف کردن با ظاهر بهره وری است.
ابزارهایی که واقعا کمک می کنند
- Claude برای خواندن یک پیام خطای طولانی و گفتن اینکه کدام خطش مهم است خوب جواب می دهد، مخصوصا وقتی متن کامل را بدون خلاصه کردن بدهید. ایران در هیچ یک از دو فهرست کشورهای پشتیبانی شده انتروپیک نیست؛ این را از صفحه خود انتروپیک خوانده ایم و نه از تست شبکه.
- Gemini فارسی را خوب می فهمد و برای پرسیدن معنی اصطلاح هایی مثل بایت کد و آپکد و کامپایل درجا کافی است. صفحه خود گوگل می گوید اپ وب جمنای در بیش از دویست و سی کشور و منطقه کار می کند و ایران در آن فهرست نیست.
- ChatGPT رایج ترین انتخاب و برای خواندن خطا قابل قبول است. ما هنوز صفحه ای برایش در بخش هوش مصنوعی نداریم، پس درباره دسترسی و قیمتش اینجا ادعایی نمی کنیم.
کجا نتیجه معکوس می دهد
خطر این کار مخصوص همین درس است: مدل به همان لایه ای جواب می دهد که سوال شما به آن اشاره کرده، و سوال یک تازه کار معمولا لایه را اشتباه نام می برد. اگر بپرسید چرا حلقه من کار نمی کند در حالی که مشکل نصب نبودن یک کتابخانه بوده، جواب مفصلی درباره حلقه می گیرید که هیچ ربطی به علت ندارد و ممکن است یک ساعت وقتتان را ببرد. علاجش در مسیر سریع همین صفحه است: اول متن کامل خطا، بعد نام لایه، بعد توضیح. لایه محیط از همه خطرناک تر است چون مدل کامپیوتر شما را نمی بیند و اگر نسخه ها را ندهید جایش را با حدس پر می کند؛ خود انتروپیک این حالت را در مستنداتش توهم می نامد و راه های کم کردنش را توضیح می دهد، یعنی سازنده هم آن را ضعف واقعی می داند. قاعده ساده است: هر چیزی که مدل درباره سیستم شما می گوید باید با یک اجرای واقعی سنجیده شود، نه با اطمینان لحنش. برای اینکه بدانید هر کدام از این ابزارها از ایران چطور قابل پرداخت است، راهنمای خرید را ببینید.
منبع ها: Anthropic: reduce hallucinations Anthropic: supported countries Google: where the Gemini web app is available
حد این توصیه
فهمیدن این زنجیره هیچ چیزی را تعمیر نمی کند؛ فقط کاری می کند که وقت تعمیر، جای درست را نگاه کنید. مدل این صفحه هم عمدا ساده شده است: کامپایل درجا، جمع آوری حافظه، بهینه سازی هایی که موتور زبان حین اجرا انجام می دهد و زمان بندی سیستم عامل هر کدام خودشان یک رشته اند و اینجا فقط اسمشان آمده. تقسیم چهارتایی لایه ها هم یک ابزار کاری است نه یک دسته بندی رسمی؛ در عمل یک خطا می تواند همزمان در دو لایه ریشه داشته باشد. و عدد opcache که در بند بعدی می آید مال سرور ماست: روی هاست شما ممکن است صفر باشد، یعنی هر درخواست بررسی کند، یا اصلا خاموش باشد.
از تجربه خود ما
روزی که این درس بررسی شد، هر دو نیمه بخش پنجم را روی همین سرور اندازه گرفتیم. اول پایتون: یک main.py که یک tools.py وارد می کند اجرا شد، و بعد از آن پوشه __pycache__ دقیقا یک فایل داشت، tools.cpython-312.pyc، و برای خود main.py هیچ چیزی نوشته نشده بود. بعد پی اچ پی: تنظیم opcache.revalidate_freq روی این سرور ۶۰ است. یک فایل داده همین بخش آموزش را یک ثانیه بعد از یک درخواست عوض کردیم و بعد هر پنج ثانیه صفحه را گرفتیم؛ تا ۴۳ ثانیه همان مقدار قدیمی برمی گشت و در ثانیه ۴۸ مقدار جدید ظاهر شد. یعنی نزدیک یک دقیقه، هر بازدیدکننده ای نسخه ای را می دید که پی اچ پی قبلا کامپایل کرده بود، در حالی که فایل روی دیسک از همان ثانیه اول عوض شده بود. اگر روی سرور خودتان دسترسی دارید، همین را با php -i | grep opcache.revalidate_freq ببینید.
سوال هایی که واقعا پرسیده می شوند
بایت کد یعنی چه؟
یک شکل میانی است: نه متن قابل خواندن برای آدم، نه دستور مستقیم پردازنده. مفسر زبان کد شما را به بایت کد تبدیل می کند و بعد خودش آن را اجرا می کند، و چون این تبدیل یک بار انجام می شود، نگه داشتنش اجرای بعدی را سریع تر می کند. فایل های داخل پوشه __pycache__ دقیقا همین اند.
چرا بعضی زبان ها سریع ترند؟
چون کار کمتری را به زمان اجرا موکول می کنند. زبانی که قبل از اجرا کامپایل می شود بیشتر تصمیم ها را از قبل گرفته، و زبانی که حین اجرا ترجمه می کند و نوع داده ها را همان لحظه می فهمد باید همان کارها را وسط اجرا انجام بدهد. ولی برای بیشتر سایت ها زبان اصلا گلوگاه نیست؛ پایگاه داده، شبکه و تصویرهای سنگین خیلی زودتر خودشان را نشان می دهند.
فایل را عوض کردم ولی سایت عوض نشد، مشکل کجاست؟
سه جواب رایج دارد و هر سه در همین زنجیره اند. یا فایلی که عوض کردید همان فایلی نیست که اجرا می شود، مثلا نسخه دیگری از پوشه دیگری بالا می آید. یا نسخه ترجمه شده قدیمی هنوز در حافظه سرور است و تا بازه بررسی نگذرد عوض نمی شود. یا خروجی از یک لایه کش جلوتر می آید و اصلا به برنامه شما نرسیده است.