شرط و حلقه در برنامه نویسی
شرط پرسشی است با جواب بله یا خیر که برنامه سر دوراهی از خودش می پرسد، و حلقه کاری است که تا وقتی شرطی برقرار باشد تکرار می شود. تقریبا هر برنامه ای که تا آخر عمر بنویسید ترکیبی از همین دوتاست، و سخت ترین خطاها دقیقا جایی می افتند که این دو به هم می رسند.
- درس ۶ از ۱۴
- مقدماتی
- رایگان، بدون ثبت نام
یک دور کامل حلقه، و تنها راه خروج از آن
- ۱ شرط را بسنج
- ۲ بدنه را اجرا کن
- ۳ یک چیز را عوض کن
- ۴ برگرد سر شرط
تنها راه بیرون آمدن از این دایره این است که شرط بالای آن نادرست شود. حلقه ای که در بدنه اش هیچ چیزی از آنچه شرط می خواند عوض نمی شود، هرگز بیرون نمی آید.
آخرین بررسی: فکت ها و نام ابزارهای این درس در همین تاریخ با منابعشان بازبینی شده اند.
شرط چیست و بدون آن برنامه چه کم دارد؟
شرط پرسشی است که فقط دو جواب دارد، بله یا خیر، و برنامه بر اساس جوابش یکی از دو مسیر را می رود. برنامه ای که هیچ شرطی ندارد یک فهرست دستور است که همیشه از اول تا آخر یکسان اجرا می شود؛ با اولین شرط، همان فهرست تبدیل به چیزی می شود که به وضعیت واکنش نشان می دهد.
age = 17
if age >= 18:
print("allowed")
else:
print("denied")خط دوم پرسش است، خط سوم مسیر بله، و خط پنجم مسیر خیر. else اختیاری است: اگر ننویسیدش، وقتی جواب خیر باشد هیچ اتفاقی نمی افتد و برنامه بی سر و صدا از شرط رد می شود. همین «بی سر و صدا» بعدا مهم می شود.
دو خطای کلاسیک اینجا کمین کرده اند. اولی گیج شدن بین یک مساوی و دو مساوی است: = یعنی مقدار بده و == یعنی مقایسه کن. پایتون در این مورد مهربان است و اگر داخل شرط یک مساوی بگذارید خطای نحوی می دهد، ولی زبان هایی مثل PHP و جاوااسکریپت که این سایت با آنها ساخته شده هیچ اعتراضی نمی کنند: if ($x = 5) مقدار می دهد و همیشه هم درست است، پس شرط شما عملا وجود ندارد و کسی هم چیزی نمی گوید.
خطای دوم ترتیب است و کمتر درباره اش حرف می زنند. وقتی چند شرط را پشت سر هم می چینید، اولین شرطی که درست باشد برنده است و بقیه اصلا سنجیده نمی شوند. پس اگر شرط کلی را بالاتر از شرط جزئی بگذارید، شرط جزئی هرگز اجرا نمی شود و شما ساعت ها دنبال خطایی می گردید که وجود ندارد. کد درست است؛ فقط ترتیبش غلط است.
یک پرسش، دو مسیر، و بقیه کد که سنجیده نمی شود
آیا شرط برقرار است؟
بدنه if اجرا می شود
- بقیه شاخه ها اصلا سنجیده نمی شوند
- برنامه بعد از بدنه به خط بعدی می رود
بدنه else اجرا می شود، اگر نوشته باشیدش
- بدون else هیچ اتفاقی نمی افتد و پیامی هم نیست
- همین سکوت، سخت ترین حالت برای پیدا کردن است
وقتی چند شرط پشت سر هم بیایند، اولین شرط درست برنده است و بقیه اصلا سنجیده نمی شوند. شرط کلی را بالای شرط جزئی نگذارید.
حلقه چیست و هر حلقه چه چیزهایی لازم دارد؟
حلقه یعنی یک کار را به جای یک بار، چند بار انجام بده. ارزشش هم در تعداد است: نوشتن سه خط برای سه فایل کار سختی نیست، ولی همان سه خط برای سی هزار فایل فقط با حلقه ممکن است، و مهم تر اینکه کدتان از تعداد مستقل می شود.
دو شکل اصلی وجود دارد. اولی روی یک مجموعه می چرخد و برای هر عضو یک بار اجرا می شود:
names = ["ali", "sara", "reza"]
for name in names:
print(name)دومی تا وقتی شرطی برقرار است می چرخد و از قبل معلوم نیست چند بار:
size = 4096
step = 0
units = ["B", "KB", "MB", "GB"]
while size >= 1024 and step < len(units) - 1:
size = size / 1024
step = step + 1هر حلقه، هر شکلی که داشته باشد، سه چیز لازم دارد و اگر یکی نباشد خراب می شود: چیزی که روی آن می چرخد یا شرطی که می سنجد، چیزی که در هر دور عوض می شود، و راهی برای تمام شدن. در نمونه دوم size و step همان چیزی اند که عوض می شوند؛ اگر خط size = size / 1024 را بردارید، شرط تا ابد درست می ماند.
یک نکته که در آموزش ها معمولا جا می ماند: نیمه دوم آن شرط، یعنی step < len(units) - 1، ربطی به اندازه ندارد. تنها کاری که می کند این است که جلوی رد شدن از آخر فهرست واحدها را بگیرد. این حلقه در کد داشبورد همین سایت هست و آن نیمه دوم تنها چیزی است که بین آن و یک خطای واقعی ایستاده.
کدام حلقه را کجا؟ یک شمارش واقعی از کد همین سایت
بیشتر آموزش ها با حلقه شمارنده شروع می کنند، همان که یک شمارنده از صفر تا عددی بالا می رود. در کد واقعی این کم استفاده ترین شکل است و برای اینکه حدس نزنیم، قالب همین سایت را شمردیم.
در ۱۹۳ فایل PHP قالب، ۵۶۰ حلقه روی مجموعه، ۲۱ حلقه شمارنده و ۱۳ حلقه شرطی وجود دارد. یعنی شکلی که اول از همه یاد می گیرید در عمل کمتر از چهار درصد حلقه های این کد است.
دلیلش ساده است: تقریبا هیچ وقت شماره عضو را نمی خواهید، خود عضو را می خواهید. وقتی می نویسید «برای هر فایل در این پوشه»، شماره فایل به کارتان نمی آید و نگه داشتن یک شمارنده فقط یک جای اضافه برای اشتباه است. آن ۲۱ مورد باقی مانده هم دقیقا جایی اند که واقعا شماره لازم است: کشیدن پنج ستاره نظر، ساختن ستون های یک جدول، رفتن عقب در ماه های یک نمودار.
سراغ آن ۱۳ حلقه شرطی هم رفتیم، چون فهرست کوتاهی بود. هفت تایشان حلقه اصلی وردپرس اند که تا وقتی نوشته ای مانده می چرخد. سه تایشان یک شناسه تکراری را یکتا می کنند: تا وقتی این اسم قبلا استفاده شده، یک شماره به آخرش اضافه کن. سه تای آخر جاهایی اند که واقعا نمی دانیم چند بار: خواندن از یک سوکت تا ته پاسخ، تبدیل بایت به واحد بالاتر، و ساختن یک نام کاربری تا وقتی تکراری نباشد.
قاعده ای که از این شمارش درمی آید و از هر تعریفی کاربردی تر است: اگر می دانید روی چه چیزی می چرخید، حلقه مجموعه ای بنویسید. اگر نمی دانید کی تمام می شود، حلقه شرطی. و اگر واقعا خود شماره را لازم دارید، آن وقت حلقه شمارنده، که در عمل کم پیش می آید.
حلقه روی مجموعه یا حلقه شرطی؟
حلقه روی مجموعه
- وقتی از قبل می دانید روی چه چیزی می چرخید
- تعداد دورها را خود مجموعه تعیین می کند
- حلقه بی پایان تقریبا غیرممکن است
- روی مجموعه خالی صفر بار اجرا می شود
- در قالب این سایت: ۵۶۰ مورد
حلقه شرطی
- وقتی نمی دانید چند بار طول می کشد
- شما باید خودتان چیزی را عوض کنید تا تمام شود
- خواندن تا ته یک پاسخ، یا ساختن یک اسم یکتا
- هر حلقه بی پایانی از این خانواده می آید
- در قالب این سایت: ۱۳ مورد
هیچ کدام از این دو بهتر از دیگری نیست؛ برای دو موقعیت متفاوت اند. شمارش ما روی این کد نشان می دهد ستون اول در عمل خیلی بیشتر لازم می شود، نه اینکه ستون دوم غلط باشد.
حلقه بی پایان روی یک سرور واقعی چه شکلی است؟
حلقه بی پایان یعنی حلقه ای که شرط توقفش هیچ وقت نادرست نمی شود. دو شکل رایج دارد و هر دو یک ریشه دارند: چیزی که شرط می خواند، در بدنه حلقه عوض نمی شود.
شکل اول فراموش کردن آن تغییر است: شرط می گوید تا وقتی شمارنده کمتر از ده است بچرخ، و داخل بدنه هیچ کس شمارنده را زیاد نمی کند. شکل دوم عوض کردن در جهت اشتباه است، که بدتر است چون کد پر از حرکت به نظر می رسد: شمارنده کم می شود در حالی که شرط منتظر زیاد شدنش است.
حالا سوالی که هیچ آموزشی جواب نمی دهد: اگر این اتفاق روی یک سایت واقعی بیفتد چه می شود؟ مرورگر که تا ابد منتظر نمی ماند. روی این سرور تنظیمی هست به اسم request_terminate_timeout که مقدارش ۱۸۰ ثانیه است. یعنی هر درخواستی که بعد از سه دقیقه هنوز تمام نشده باشد، پردازه اش کشته می شود.
و کلمه «کشته» اینجا دقیق است، نه استعاره. این یک خطای PHP نیست که بتوانید بگیریدش؛ پردازه از بیرون بسته می شود. نتیجه اش این است که کدهای پایانی شما اجرا نمی شوند: اگر حلقه وسط نوشتن یک فایل بوده، فایل نیمه کاره می ماند، و اگر قفلی گرفته بوده، قفل آزاد نمی شود. برای بازدیدکننده هم چیزی که می بیند یک صفحه خطای کلی سرور است که هیچ اشاره ای به حلقه شما ندارد.
پس مهارتی که باید بردارید این است: حلقه بی پایان را از پیام خطا پیدا نمی کنید، چون پیام خطایی در کار نیست. از روی زمان پیدایش می کنید. صفحه ای که همیشه دقیقا حوالی همان سه دقیقه شکست می خورد، تقریبا همیشه یک حلقه است یا انتظاری که تمام نمی شود.
شرط داخل حلقه: break، continue و خطای یکی
جای واقعی این دو مفهوم کنار هم است: حلقه ای که برای هر عضو یک تصمیم می گیرد. همان برنامه پنج خطی درس اول همین بود، حلقه ای روی فایل ها با یک شرط روی اندازه.
دو کلمه اینجا کار را عوض می کنند. break کل حلقه را رها می کند و می رود بیرون؛ continue فقط همین دور را رها می کند و سراغ عضو بعدی می رود. اولی برای وقتی است که جوابتان را پیدا کرده اید، دومی برای وقتی که این عضو به دردتان نمی خورد.
غافلگیری ای که یک بار برای هر کسی پیش می آید: اگر یک حلقه داخل حلقه دیگر باشد، break فقط حلقه داخلی را می شکند، نه هر دو را. برنامه از داخلی می آید بیرون و بی سر و صدا دور بعدی حلقه بیرونی را شروع می کند. کد هیچ اعتراضی نمی کند و شما فکر می کنید بیرون آمده اید.
خطای دیگری که اسم خودش را دارد، خطای یکی است: حلقه یک بار بیشتر یا یک بار کمتر از آنچه انتظار داشتید اجرا می شود. منبع اصلی اش این است که بازه ها معمولا از ابتدا بسته و از انتها بازند. در پایتون range(1, 10) اعداد یک تا نه را می دهد و ده را نمی دهد. این تصمیم عجیب نیست و دلیل دارد، ولی حفظ کردنش جواب نمی دهد؛ چیزی که جواب می دهد این است که یک بار بنویسید و خروجی را بشمرید.
و حالت آخر، که ساکت ترین حالت است: حلقه روی یک مجموعه خالی. صفر بار اجرا می شود، هیچ خطایی نمی دهد و هیچ چیزی چاپ نمی کند. برنامه ای که هیچ کاری نکرده و هیچ شکایتی هم ندارد، همیشه اولین چیزی است که باید بررسی کنید، نه آخرین.
حلقه ای را که خودتان ننوشته اید چطور بخوانید؟
این مهارت امروز از نوشتن حلقه مهم تر است، چون کد بیشتر خوانده می شود تا نوشته و حالا بخش بزرگی از آن کد را هم شما ننوشته اید. روش ما چهار پرسش است و ترتیبشان اهمیت دارد.
یک: روی چه چیزی می چرخد و آن چیز می تواند خالی باشد؟ اگر بتواند، حلقه صفر بار اجرا می شود و این معمولا همان حالتی است که کسی تستش نکرده.
دو: در هر دور چه چیزی عوض می شود؟ اگر جوابتان «هیچ چیز» بود، یا حلقه بی پایان است یا حلقه اضافه است.
سه: چه چیزی متوقفش می کند و آیا ممکن است هرگز پیش نیاید؟ اینجا دنبال breakها هم بگردید، چون خروج زودهنگام همیشه بالای کد نوشته نمی شود.
چهار: بعد از آخرین دور چه چیزی درست است؟ متغیرهایی که داخل حلقه ساخته شده اند بعد از آن چه مقداری دارند و آیا کسی به آن مقدار تکیه کرده؟
این چهار پرسش را روی همان حلقه تبدیل واحد بالا امتحان کنید: روی هیچ مجموعه ای نمی چرخد پس خالی بودن معنا ندارد، دو چیز عوض می شود، شرط دو نیمه دارد که یکی از آنها فقط محافظ است، و بعد از آخرین دور step شماره واحد درست را نگه داشته و همان است که در خط بعد استفاده می شود. چهار جواب، و حالا حلقه را می فهمید بدون اینکه اجرایش کنید.
مسیر سریع با هوش مصنوعی
چیزی که در این موضوع واقعا عوض شده نوشتن حلقه نیست؛ نوشتن حلقه هیچ وقت هم سخت نبود. چیزی که عوض شده این است که حالا روزی ده تا حلقه به دستتان می رسد که ننوشته اید و باید در سی ثانیه بفهمید هر کدام کجا می شکند. پس دستور زیر از مدل کد نمی خواهد؛ از او می خواهد کد را به همان چهار پرسش بالا جواب بدهد. برای این کار یک مدل قوی بهتر از یک مدل سریع و ارزان جواب می دهد، چون کار قضاوت است؛ انتخاب فعلی ما میان مدل های کدنویسی، در بخش هوش مصنوعی همین سایت آمده است.
- کل حلقه را با چند خط بالا و پایینش کپی کنید، نه فقط خود حلقه. مقداری که شرط می خواند معمولا بیرون حلقه ساخته شده و بدون آن، جواب مدل حدس است.
- دستور زیر را بدهید و صریح بگویید کد را بازنویسی نکند. اگر اجازه بازنویسی بدهید، به جای توضیح یک نسخه تازه می گیرید و اولین چیزی که از دست می رود همان حالتی است که می خواستید بفهمید.
- جواب حالت خالی را جدی بگیرید. در تجربه ما بیشترین چیزی که این دستور رو می کند همین است: حلقه ای که روی یک فهرست خالی صفر بار می چرخد و هیچ کس در کد بعدش این احتمال را در نظر نگرفته.
- قبل از قبول کردن جواب، خودتان حلقه را با یک ورودی خالی و یک ورودی یک عضوی اجرا کنید. این دو حالت ارزان ترین تستی هستند که وجود دارد و بیشتر خطاهای حلقه همان جا بیرون می زنند.
نسخه آماده کپی
این کد مال من نیست و می خواهم بفهممش، نه اینکه عوضش کنم.
{کد را اینجا بگذارید، با چند خط بالا و پایین حلقه}
کد را بازنویسی نکن. فقط این چهار پرسش را جواب بده، هر کدام دو جمله:
۱. این حلقه روی چه چیزی می چرخد و آن چیز می تواند خالی باشد؟ اگر خالی باشد چه اتفاقی می افتد؟
۲. در هر دور دقیقا چه متغیرهایی عوض می شوند؟
۳. چه چیزی حلقه را متوقف می کند، و آیا حالتی هست که هرگز متوقف نشود؟ خروج های زودهنگام را هم بگو.
۴. بعد از آخرین دور، هر متغیری که داخل حلقه ساخته شده چه مقداری دارد؟
در آخر بگو کدام یک از این چهار جواب را از روی خود کد نمی توانی مطمئن باشی و برای دانستنش چه چیزی لازم داری.
قبل از اعتماد به خروجی: آخرین بند دستور مهم ترین بند آن است و اگر برش دارید کل دستور را خراب کرده اید. مدل چیزی را که نمی بیند هم با همان لحن مطمئن توصیف می کند، و چیزی که در این حالت نمی بیند معمولا همان جایی است که مجموعه ساخته می شود. پس هر جوابی درباره خالی بودن یا تمام شدن، تا وقتی خودتان یک بار با ورودی خالی اجرا نکرده اید، فرضیه است نه واقعیت.
هوش مصنوعی در این کار
حلقه و شرط دقیقا آن دسته از کدهایی هستند که مدل های زبانی بی نقص می نویسند. هزاران نمونه از همین ساختارها در داده آموزششان بوده و نتیجه اش این است که تولیدشان دیگر مهارت به حساب نمی آید. پس کاری که برای شما می ماند خواندن است: تشخیص اینکه این حلقه کجا می شکند، این شرط چه حالتی را جا انداخته، و آیا این کد روی ورودی خالی همان کاری را می کند که فکر می کنید. موضع ما این است که برای این موضوع مدل را در نقش توضیح دهنده بگذارید، نه نویسنده؛ کدی که خودتان ننوشته اید و نمی توانید خط به خط توضیحش بدهید، کد شما نیست.
ابزارهایی که واقعا کمک می کنند
- Claude برای برداشتن یک تکه کد و توضیح دادن رفتارش خوب جواب می دهد و وقتی صریح بگویید بازنویسی نکن، کمتر از خودش نسخه تازه می سازد. ایران در هیچ یک از دو فهرست کشورهای پشتیبانی شده انتروپیک نیست؛ این را از صفحه خود انتروپیک خوانده ایم و نه از تست شبکه.
- Claude Code ابزار خط فرمان است و برتری اش برای این کار یک چیز است: به جای یک تکه کد، خود مخزن را می بیند، پس می تواند برود ببیند مجموعه ای که حلقه روی آن می چرخد از کجا آمده. خودش رایگان نصب می شود ولی بدون اشتراک کلود یا حساب کنسول انتروپیک کار نمی کند، و ایران در فهرست کشورهای پشتیبانی شده نیست.
- Gemini حالت جداگانه ای برای درس خواندن دارد به اسم Guided Learning که به جای دادن جواب سوال می پرسد، و برای همین چهار پرسش خواندن حلقه انتخاب مناسبی است. صفحه خود گوگل می گوید اپ وب جمنای در بیش از دویست و سی کشور و منطقه کار می کند و ایران در آن فهرست نیست.
کجا نتیجه معکوس می دهد
خطر مشخص این موضوع این است: مدل درباره یک حلقه با اطمینان کامل حرف می زند در حالی که مجموعه ای را که حلقه روی آن می چرخد اصلا ندیده. اگر آن مجموعه از یک کوئری پایگاه داده یا یک درخواست شبکه بیاید و شما فقط خود حلقه را کپی کرده باشید، جمله «این حلقه امن است» یک حدس است که شبیه یک واقعیت نوشته شده. انتروپیک خودش این حالت را در مستنداتش توهم می نامد و روش های کم کردنش را توضیح می دهد، یعنی سازنده هم آن را ضعف واقعی می داند نه شایعه.
خطر دوم عملی تر است: وقتی از مدل می خواهید یک حلقه کند را سریع تر کند، نسخه ای که برمی گرداند اغلب روی حالت های لبه رفتار متفاوتی دارد، مخصوصا روی ورودی خالی و روی ورودی تک عضوی. پس هر بازنویسی را با همان دو ورودی امتحان کنید، نه با ورودی معمولی که همیشه کار می کند. برای اینکه بدانید هر کدام از این ابزارها از ایران چطور قابل پرداخت است، راهنمای خرید را ببینید، و برای انتخاب فعلی ما میان مدل های کدنویسی، بهترین هوش مصنوعی برای کدنویسی.
منبع ها: Anthropic: reduce hallucinations Anthropic: supported countries Claude Code: overview and install Google: where the Gemini web app is available
حد این توصیه
نحو این صفحه پایتون است. شکل شرط و حلقه در هر زبانی هست، ولی نوشتنش فرق می کند و کپی کردن مستقیم این خط ها در PHP یا جاوااسکریپت کار نمی کند. دوم اینکه این درس عمدا سراغ بازگشت، مولدها و درک لیستی نرفته؛ آنها راه های دیگری برای همین کارند و وقتی معنا پیدا می کنند که این دو ساختار پایه جا افتاده باشند. و سوم، جایی که احتمالا منتظرش بودید: اینکه کدام حلقه سریع تر است. ما هیچ عددی درباره سرعت نمی دهیم، چون روی اندازه هایی که یک تازه کار با آن کار می کند تفاوت قابل اندازه گیری نیست و ما هم آن را روی این سرور نسنجیده ایم. حلقه ای که درست است و خوانده می شود، از حلقه ای که شاید سریع تر باشد بهتر است.
از تجربه خود ما
روزی که این درس بررسی شد، به جای حدس زدن، فایل پیکربندی PHP همین سرور را باز کردیم. در /www/server/php/85/etc/php-fpm.conf خطی هست که می گوید request_terminate_timeout = 180. یعنی هر درخواستی که بعد از ۱۸۰ ثانیه هنوز تمام نشده باشد، پردازه اش از بیرون بسته می شود. راهنمای رسمی PHP هم همین را می گوید: این تنظیم مهلتی است که بعد از آن پردازه کارگر کشته می شود. نتیجه ای که برای این درس مهم است و در هیچ آموزش حلقه ای نوشته نشده این است که چون پردازه کشته می شود و خطایی رخ نمی دهد، کدهای پایانی شما اجرا نمی شوند. حلقه ای که وسط نوشتن یک فایل بوده، فایل را نیمه کاره جا می گذارد. همین شمارش را روی قالب سایت هم انجام دادیم و در ۱۹۳ فایل PHP به ۵۶۰ حلقه روی مجموعه، ۲۱ حلقه شمارنده و ۱۳ حلقه شرطی رسیدیم؛ هر سه عدد را با یک جستجوی ساده روی همان پوشه می شود دوباره به دست آورد.
سوال هایی که واقعا پرسیده می شوند
فرق for و while در عمل چیست؟
در عمل فرقشان این است که چه کسی تعداد دورها را تعیین می کند. در حلقه روی مجموعه، خود مجموعه تعیین می کند و شما کاری نمی کنید؛ در حلقه شرطی، شما باید خودتان چیزی را عوض کنید وگرنه تمام نمی شود. به همین دلیل هم تقریبا همه حلقه های بی پایان از نوع دوم اند.
break و continue را کجا باید استفاده کنم؟
وقتی جوابتان را پیدا کردید و ادامه دادن حلقه فایده ای ندارد، break. وقتی این عضو خاص به دردتان نمی خورد ولی بقیه ممکن است بخورند، continue. یک هشدار: اگر حلقه ها تو در تو باشند، break فقط حلقه داخلی را می شکند و برنامه بی سر و صدا دور بعدی حلقه بیرونی را شروع می کند.
چرا حلقه من یک بار کمتر یا بیشتر از انتظارم اجرا می شود؟
اسم این خطای یکی است و منبع اصلی اش این است که بازه ها معمولا از ابتدا بسته و از انتها بازند: در پایتون range(1, 10) اعداد یک تا نه را می دهد. راه حل حفظ کردن نیست؛ یک بار همان بازه را چاپ کنید و اعضایش را بشمرید، بعد از آن دیگر اشتباه نمی کنید.