تصور کنید دستیاری دارید که دقیقاً میداند کجای کد شما غلط است، اما تا زمانی که خودتان دلیل خطا را حدس نزنید، لبهایش را میبندد. CodeTeach دقیقاً برای همین هدف طراحی شده است: آزار دادنِ هدفمند برنامهنویس برای یادگیری عمیقتر.
در حالی که هر دستیار کدنویسی بزرگی، از ChatGPT گرفته تا Cursor، تمام تلاش خود را میکنند تا سریعترین مسیر رسیدن به کدِ سالم را پیدا کنند، CodeTeach عمداً از ارائه پاسخ مستقیم امتناع میکند. این ابزار کاربر را مجبور میکند تا پیش از دریافت حتی یک راهنمایی کوچک، با باگها دستوپنجه نرم کند، فرضیه بسازد و از طریق استدلال، مسیر حل مشکل را پیدا کند.
این رویکرد در زمانی معرفی میشود که توسعهدهندگان بهشدت به هوش مصنوعی وابسته شدهاند تا فقط «مشکل را حل کند». این وابستگی باعث ایجاد شکافی در مهارتهای بنیادی عیبیابی (Debugging) شده است؛ وضعیتی که در آن AI مشکل را حل میکند اما انسان هیچچیز نمیآموزد. CodeTeach با تبدیل «امتناع از پاسخ» به یک ویژگی (Feature)، سعی دارد ارزش آموزشی «تلاش سازنده» (Productive Struggle) را بازگرداند.
همانطور که در تحلیلهای پیشین ما دربارهی امنیت مدلهای بازمتن و اهمیت درک عمیق ساختار کد اشاره کردیم، اتکای مطلق به خروجیهای آماده، ریسکهای فنی بلندمدتی دارد. حالا CodeTeach میخواهد این روند را در آموزش معکوس کند. این رویکرد در واقع پاسخی به شکست عاملهای عیبیابی هوش مصنوعی است که به دلیل نبود حلقههای بازتولید خطا نمیتوانند یادگیری واقعی ایجاد کنند.
به نقل از گزارشی که در ۲۲ سپتامبر ۲۰۲۶ در dev.to منتشر شد، این ابزار مانند یک ماشین وضعیت (State Machine) سختگیرانه عمل میکند. وقتی دانشجویی کد خراب خود را ارسال میکند، سرور بلافاصله اصلاحیه نمیسازد، بلکه یک فرآیند پنجمرحلهای را اجرا میکند:
۱. کد را از طریق یک طبقهبندیکننده (Classifier) عبور میدهد تا نوع باگ را شناسایی کند (مثلاً خطای off-by-one، ارجاع به مقدار null یا تغییر در آرایه هنگام پیمایش).
۲. بررسی میکند که آیا دانشجو برای این تلاش خاص، فرضیهای نوشته است یا خیر.
۳. اگر فرضیهای وجود نداشته باشد، از ارائه هرگونه راهنمایی امتناع میکند.
۴. در صورت وجود فرضیه، بهجای پاسخ، یک پرسش سقراطی تولید میکند.
۵. الگوهای خطا را ثبت میکند تا دانشجو بعداً بتواند تمایلات و اشتباهات تکراری خود را از طریق یک «داشبورد نقاط ضعف» بررسی کند.
مهمترین گیتِ این سیستم، الزام به ارائه فرضیه است. پیش از اینکه در هر سطح و هر بار راهنمایی ارائه شود، دانشجو باید یک جمله بنویسد: «فکر میکنم باگ به این دلیل است که...».
اگر کاربر بدون این فرضیه درخواست راهنمایی کند، سرور یک پاسخ JSON برمیگرداند که صراحتاً نیاز به فرضیه را اعلام میکند:
{
"requiresHypothesis": true,
"prompt": "Before I give you a hint, tell me in one sentence what you think the bug is.",
"hintLevel": 1
}
این سازوکار مانع از «اسپم کردنِ دکمه راهنما» میشود و کاربر را به متاتفکر شدن (Metacognition) وا میدارد. هر پله از نردبان راهنمایی، به جای یک کلیک، به یک «فکر» نیاز دارد. پس از نوشته شدن فرضیه، آن را در یک جدول اختصاصی در کنار سطح راهنمایی که باز کرده است ذخیره میکند و گیت برای درخواست بعدی دوباره فعال میشود.
نردبان چهارپلهای راهنمایی
راهنماییها در CodeTeach بر اساس تلاش کاربر پیش میروند، نه زمان. سرور تنها زمانی به سطح بعدی میرود که کاربر دو بار تلاش واقعی کرده باشد، به این معنا که واقعاً کد خود را تغییر داده باشد. این امر تضمین میکند که ابزار تبدیل به یک دکمه راهنمای ساده نشود، بلکه نردبانی باشد که به تلاش پاسخ میدهد.
- سطح ۱: یک اشاره مبهم که به ناحیهای گسترده از کد اشاره میکند.
- سطح ۲: راهنمایی دقیقتر که تمرکز را روی یک خط یا عبارت خاص میبرد.
- سطح ۳: پاسخی نزدیک که نوع باگ را نام میبرد اما راه حل نهایی را نمیگوید.
- سطح ۴: یک چارچوب نهایی که از دانشجو میخواهد مورد خاص (Edge Case) را با زبان خودش توضیح دهد.
از نظر فنی، این سیستم به صورت یک ماشین وضعیت کوچک برای هر جفت (دانشجو، تمرین) مدیریت شده و در SQLite ذخیره میشود. سیستم متغیرهای currentLevel (سطح فعلی)، attemptsAtLevel (تلاشها در این سطح)، totalAttempts (کل تلاشها) و resolved (حل شده) را ردیابی میکند. وقتی تلاشی جدید با وضعیت passed: false میرسد، مقدار attemptsAtLevel افزایش مییابد. در صورت رسیدن به عدد ۲، سطح ارتقا یافته و شمارنده ریست میشود. توسعهدهنده اشاره کرده که سختترین بخش پیادهسازی، مقاومت در برابر وسوسه برای اضافه کردن وضعیتهای بیشتر به این ماشین بود.
تحلیل فنی و تحلیل استاتیک
برای شناسایی باگها بدون اتکای مطلق به مدل زبانی بزرگ (LLM) — که مثل کتابخانهداری است که میلیاردها صفحه را خوانده و حالا با همان لحن جواب میدهد — توسعهدهنده یک پلاگین اختصاصی برای ESLint ساخته است. این طبقهبندیکننده، دو منبع را ترکیب میکند: عبارتهای منظم (Regex) خطاهای زمان اجرا و تحلیل استاتیک (Static Analysis).
از آنجا که Regexها فقط علائم را میبینند (مثلاً میگویند TypeError: Cannot read property 'x' of undefined) اما نمیتوانند بین ده باگ مختلف که یک رشته خطای یکسان تولید میکنند تفاوت قائل شوند، تحلیل استاتیک وارد عمل میشود. برای مثال، جاوااسکریپت برای بسیاری از خطاهای منطقی مختلف، خطای property of undefined میدهد، اما نمیگوید که «شرط حلقه شما باید < باشد نه <=».
تحلیل استاتیک این شکاف را پر میکند. توسعهدهنده یک پلاگین ESLint کوچک با سه قانون خاص با استفاده از بازدیدکنندگان AST (درخت نحو انتزاعی) نوشت:
- loop-bound-heuristic: شناسایی الگوهای رایج خطای off-by-one. این قانون
ForStatementرا بررسی میکند تا ببیند آیا تست<=در برابر یک عبارت.lengthاست یا حلقهها با ۱ شروع شده و از مرز سختگیرانه<استفاده میکنند. این قانون حدود ۴۰ خط کد است. - mutation-in-iteration: تشخیص زمانی که دانشجو در حال پیمایش یک آرایه، آن را تغییر میدهد (مثلاً
arr.push(x)یاarr.splice()داخل یک حلقه روی همانarr). این باگها اغلب باعث پرش از روی عناصر یا ایجاد حلقههای بینهایت میشوند. این پیچیدهترین قانون است زیرا باید بدنه حلقه را برای یافتن فراخوانی متدها روی همان شناسه، بدون تحلیل کامل Scope، پیمایش کند. - async-missing-await: شناسایی فراخوانی توابعی مثل
fetchیاsaveکه Promise حاصل از آنها هرگز await نشده، بازگردانده نشده یا با.then()مدیریت نشده است. این قانون باگهایی را میگیرد که کد «کار میکند» اما مقدار خروجی به جای نتیجه نهایی، یک Promise است.
این قوانین به عنوان داده در آرایه پیکربندی (Flat Config) در ESLint 9 ارسال میشوند. توسعهدهنده اشاره کرد که API قدیمی linter.defineRule() با Flat Config سازگار نیست و این کشف باعث سردرگمی و دیباگهای اولیه شد، جایی که سیستم خطای «این متد نمیتواند با flat config استفاده شود» میداد.
چالشهای ادغام (Integration)
ساخت این ابزار یک باگ کلاسیک در لایههای ادغام را آشکار کرد. طبقهبندیکننده لیستی از اشیاء PatternCandidate تولید میکند که هر کدام دارای یک سطح اطمینان (Confidence) و یک منبع (eslint یا error-output) هستند. هدف این بود که سیگنال ESLint ترجیح داده شود چون به ریشه مشکل اشاره میکند، در حالی که رشتههای خطا فقط علائم را توصیف میکنند.
در نسخه اول، تابع pickTop صرفاً به دنبال بالاترین اطمینان (high یا medium) میگشت:
function pickTop(candidates: PatternCandidate[]): MistakePattern | null {
const strong = candidates.find(c => c.confidence === 'high' || c.confidence === 'medium');
return strong?.pattern ?? null;
}
اما چون اکتشافات مبتنی بر خروجی خطا (error-output) همیشه اطمینان بالایی داشتند (چون خطا واقعاً رخ داده است)، همیشه برنده میشدند. در نتیجه، دانشجویی با خطای off-by-one در حلقه، بهجای اینکه به شرط خروج حلقه ارجاع داده شود، این پیام را میگرفت: «کدام متغیر ممکن است undefined باشد؟».
راه حل، اولویت دادن به منبع بود:
function pickTop(candidates: PatternCandidate[]): MistakePattern | null {
const fromEslint = candidates.find(c => c.source === 'eslint' || c.source === 'both');
if (fromEslint) return fromEslint.pattern;
const strong = candidates.filter(c => c.confidence === 'high' || c.confidence === 'medium');
return strong[0]?.pattern ?? null;
}
با این حال، باگ دومی در تابع generateFallbackHint باقی مانده بود. این تابع مستقیماً candidates[0] را میخواند و تابع pickTop را کاملاً دور میزد. چون لیست بر اساس اطمینان مرتب شده بود، مسیر جایگزین (Fallback) همچنان الگوی غلط را برمیگرداند. توسعهدهنده تنها با اجرای مستقیم طبقهبندیکننده و مقایسه خروجی با پاسخ سرور HTTP متوجه این موضوع شد. اصلاح آن مستلزم تغییر fallback برای استفاده از c.topPattern بود تا هر مسیر کد از یک منطق انتخاب یکسان استفاده کند.
مدلهای زبانی در برابر قالبهای پیشفرض
CodeTeach از یک سیستم دوگانه برای تولید پیامها استفاده میکند. اگر مدل Claude در دسترس باشد، پرسشهای سقراطی محاورهای میسازد که دقیقاً با کد و فرضیه دانشجو متناسب است.
اما برای جلوگیری از توقف سرویس در زمان قطعی API، محدودیتهای نرخ (Rate Limits) یا هزینههای بالا، یک سیستم «قالبهای پیشفرض» (Template Fallbacks) تعبیه شده است. توسعهدهنده استدلال میکند که اگر محصولی فقط به یک API خارجی وابسته باشد، یک «دمو» است، نه یک «محصول». تفاوت بین «کار میکند وقتی همه چیز خوب است» و «کار میکند»، در همین سیستم fallback است. این رویکرد برای جلوگیری از خطاهای عملیاتی مشابه مشکلات فراموشی دادهها در عاملهای هوش مصنوعی طراحی شده تا پایداری سیستم تضمین شود.
این راهنماییهای پیشفرض، کلی (Pattern-generic) هستند زیرا سیستم در حالت fallback نمیتواند کد دانشجو را ببیند و فقط الگوی شناسایی شده را میشناسد. برای خطاهای off-by-one، بانک سوالات شامل مواردی چون اینهاست:
- «یک بار دیگر به نحوه توقف حلقه نگاه کن. آیا این همان مرزی است که واقعاً میخواهی؟»
- «متغیر حلقه شما در آخرین تکرار چه مقداری دارد — و آیا این یک موقعیت معتبر است؟»
- «حلقه خود را برای ورودی با اندازه ۱ به صورت دستی دنبال کن. بدنه حلقه چند بار اجرا میشود؟»
این مسیر fallback همچنین به عنوان یک ابزار توسعه عمل میکند؛ اگر نتوان یک راهنمایی معقول از طریق قالبها تولید کرد، احتمالاً آن الگو برای مفید بودن (حتی با LLM) بیش از حد مبهم است.
سیگنالهای دادهای و تحلیل رفتار
شگفتانگیزترین خروجی این طراحی، دادههای تولید شده است. برخلاف ابزارهای رایج که فقط کد نهایی را ثبت میکنند، CodeTeach جریانی از جملات ساختاریافته درباره این موضوع ثبت میکند که دانشجویان در لحظات خاص عیبیابی، «چه چیزی را غلط میپندارند». این سیستم «تئوری باگ» از دیدگاه دانشجو را در لحظه درخواست کمک ثبت میکند.
این موضوع یک سیگنال منحصربهفرد برای مدرسان فراهم میکند تا خوشههایی از تصورات غلط رایج را شناسایی کنند. این امر ابزار را از یک عیبیاب ساده به سیستمی تبدیل میکند که استدلال انسانی را میفهمد، نه فقط خروجی کد را. پیادهسازی این بخش نیازمند یک ستون اضافی در hint_sessions به نام hypothesis_pending و یک جدول جدید برای hypotheses بود، همراه با یک بررسی گیت در ابتدای Route Handler.
پرسشهای باز درباره پداگوژی (آموزش)
با وجود موفقیت فنی، توسعهدهنده درباره «اصطکاک» (Friction) ابزار ابراز تردید میکند. برای دانشجویی که روی یک غلط تایپی ساده گیر کرده است — مثلاً let i = 0; i i <= arr.length; i++ — الزام به نوشتن فرضیه ممکن است اتلاف وقت باشد. اگرچه الگوی خطای سینتکس پیشنهاد میکند خط را کاراکتر به کاراکتر بخوانید، اما این همچنان یک رویکرد غیرمستقیم است.
مرز باریکی بین «تلاش سازنده» و «ناامیدی مطلق» وجود دارد. در حال حاضر، ابزار فاقد یک مطالعه رسمی است تا ثابت کند گیتِ فرضیه واقعاً نتایج یادگیری بلندمدت را بهبود میبخشد. توسعهدهنده اشاره میکند که «ثبت کردن (Logging) به معنای یادگیری نیست». برای اثبات این تئوری، مقایسه پیش و پس از آموزش با یک گروه کنترل لازم است.
علاوه بر این، مشکلی در داشبورد نقاط ضعف وجود دارد. چون داشبورد فقط الگوهایی را ردیابی میکند که طبقهبندیکننده قادر به شناسایی آنهاست، این داشبورد بازتابدهنده «آنچه سیستم میبیند» است، نه لزوماً «تمام مشکلاتی که دانشجو با آنها دستوپنجه نرم میکند». اگر دانشجویی با باگی دستوپنجه نرم کند که سیستم قانونی برای آن ندارد، آن مشکل نامرئی میماند.
نقشه راه آینده
برای تبدیل این پروتوتایپ به یک ابزار کلاسی، سه ویژگی برنامهریزی شده است:
- نوبتهای پسمرگ (Post-mortem): پس از رفع باگ، LLM از دانشجو میخواهد به زبان خودش توضیح دهد که چرا باگ رخ داده بود. این توضیح به صورت کلی امتیازدهی شده و در دستهبندی نقاط ضعف ذخیره میشود تا حلقه یادگیری بسته شود.
- اثر انگشت مشترک دانشجویان (Cross-student fingerprinting): اگر ۴۰٪ از یک کلاس در یک تمرین خاص با خطای off-by-one مواجه شوند، سیستم آن را برای مدرس علامتگذاری میکند تا یک جلسه توضیح زنده یا درس کوتاه برگزار کند. این ویژگی است که باعث میشود یک مدرس واقعاً بخواهد از این ابزار استفاده کند.
- تایمرهای تلاش (Struggle timers): یک قفل قابل تنظیم توسط مدرس که مانع از دریافت راهنماییهای سطح ۱ برای N دقیقه تلاش مستقل میشود و این تأخیر را به عنوان «تلاش سازنده» تعریف میکند، نه دریغ کردن کمک.
این تغییر در طراحی AI نشان میدهد که نسل بعدی ابزارهای آموزشی ممکن است ارزش بیشتری در «آنچه از انجام دادن امتناع میکنند» بیابند تا «آنچه میتوانند خودکار کنند». پروژه در github.com/janabi54/codeteach-debug-tutor در دسترس است.
گام بعدی شما
- اگر مدرس هستید، از متد «پرسش پیش از پاسخ» در جلسات رفع اشکال استفاده کنید تا وابستگی دانشجویان به AI کم شود.
- برای تمرین عیبیابی، سعی کنید پیش از هر بار پرسش از ChatGPT، فرضیه خود را روی کاغذ بنویسید.
- مخزن این پروژه را در
github.com/janabi54/codeteach-debug-tutorبررسی کنید تا با پیادهسازی ماشین وضعیت در آموزش آشنا شوید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو