تصور کنید یک وبهوک پرداخت که روزانه ۳۰۰ درخواست را پردازش میکند، ناگهان در ۲۰ اوت ۲۰۲۶ ثبت پرداختها را متوقف کند، اما داشبورد نظارتی شما همچنان عدد «صفر خطا» را نشان دهد. این فاجعه زمانی رخ داد که یک برنامهنویس برای بازنویسی بخش مدیریت خطا، از یک مدل رایگان در پلتفرم MonkeyCode استفاده کرد؛ نتیجه این بود که هر کرش سیستمی به یک پاسخ جعلی «200 OK» تبدیل شد.
این سناریو تضاد عمیقی را در توسعه مدرن نشان میدهد: تقابل بین کد «تمیز» و کد «مشاهدهپذیر». در محیطهای عملیاتی، یک شکست بلند و پر سر و صدا — مثل خطای 500 Internal Server Error — بسیار ارزشمندتر از یک شکست خاموش است، چون هشدارها را فعال کرده و امکان اصلاح فوری را فراهم میکند.
همانطور که در تحلیلهای پیشین ما دربارهی امنیت مدلهای بازمتن اشاره کردیم، اعتماد کورکورانه به خروجیهای مدل بدون لایهی نظارتی، ریسکهای سیستمی را افزایش میدهد. پیش از این تغییر، وبهوک مذکور نرخ خطای طبیعی ۲ تا ۳ درصدی داشت. هندلر قدیمی شاید ظریف نبود، اما یک مزیت حیاتی داشت: وقتی شکست میخورد، فریاد میزد. اگر تابع processPayment خطا میداد، کلاینت وضعیت ۵۰۰ دریافت میکرد، ردیاب خطاها اثر (stack trace) را ثبت میکرد و ارائهدهنده پرداخت بهطور خودکار عملیات را تکرار میکرد.
برنامهنویس در جستجوی تابآوری بیشتر، از دسترسی رایگان به مدلهای MonkeyCode استفاده کرد. مدل پیشنهاد داد از یک بلوک try-catch برای «مدیریت متین خطاها» استفاده شود. استدلال مدل این بود که ارائهدهندگان وبهوک، کد ۲۰۰ را به معنای «تحویل موفق، تکرار نکن» میفهمند؛ اما مدل این منطق را به تمام خطاها، حتی قطعیهای موقت پایگاهداده که نیاز به تکرار داشتند، تعمیم داد. این چالش با مشکلات پایداری در APIها و شکست مکانیزمهای تکرار ساده که پیشتر بررسی کردیم، شباهت زیادی دارد.
بر اساس گزارشی در dev.to، این جایگزینیِ تولیدشده توسط AI، بلوکی ایجاد کرد که فقط یک پیام خطای کوتاه در کنسول چاپ میکرد و فارغ از نتیجه، وضعیت موفقیت را به کلاینت برمیگرداند. در واقع، هرگونه خطای سیستمی در لایهی کد مخفی میشد و برای دنیای بیرون به شکل یک تراکنش موفق جلوه میکرد.
تحلیل فنی نشان میدهد منطق مدل در سه نقطه کلیدی شکست خورد:
- سرکوب تکرار (Retry Suppression): با بازگرداندن کد ۲۰۰ برای خطاهای موقت دیتابیس (Transient Timeouts)، سرور به ارائهدهنده پرداخت اعلام کرد که رویداد با موفقیت تحویل شده است. این کار باعث شد مکانیسم تکرار خودکار (Automatic Retry) ارائهدهنده پرداخت کاملاً متوقف شود.
- از دست رفتن زمینه (Context Loss): لاگهای جدید شناسههای رویداد (Event IDs)، شناسهی مشتری (Customer IDs) و اثر خطا (Stack Traces) را حذف کردند. سیستم فقط پیامهای کلی مثل «پرداخت شکست خورد: زمان اتصال به پایان رسید» را ثبت میکرد. این موضوع باعث شد تطبیق دادن خطاها با تراکنشهای خاص عملاً غیرممکن شود. در همین راستا، استفاده از سرورهای بازتولید خطا به عنوان جایگزینی کارآمدتر برای تحلیل لاگهای ناقص در عیبیابی کلاینتهای AI مطرح شده است.
- کوریِ تستها (Test Blindness): تستهای واحد (Unit Tests) فقط مسیرهای موفق را شبیهسازی (Mock) میکردند. این یعنی بلوک catch — و باگی که درون آن بود — هرگز در طول فرآیند CI/CD اجرا نشد. تستها فقط بررسی میکردند که اندپوینت پاسخ ۲۰۰ برگرداند، و کد جدید این کار را به شکلی بینقص انجام میداد.
برنامهنویس در نهایت نه از طریق هشدارها، بلکه از طریق تیکتهای پشتیبانی مشتریان متوجه مشکل شد؛ مشتریانی که شکایت میکردند هزینهها از حسابشان کسر شده اما پرداختی ثبت نشده است. چون هر درخواست پاسخ ۲۰۰ میداد، هیچ هشداری در سیستم نظارتی فعال نشد.
هنگام بررسی داشبورد ارائهدهنده پرداخت، تمام رویدادها با وضعیت «تحویل شده» (Delivered) علامتگذاری شده بودند. سرنخ نهایی زمانی پیدا شد که یک بازرسی دستی روی دیتابیس سرور رایگان MonkeyCode انجام شد و فاش گشت که از زمان استقرار کد جدید، هیچ رکورد پرداختی جدیدی ایجاد نشده است. این باگ تنها به این دلیل قابل شناسایی بود که دیتابیس در گزینه سرور رایگان به قدری کوچک بود که میشد آن را مستقیماً بازرسی کرد. برای ارزیابی دقیقتر این نقاط ضعف در سرویسهای رایگان، استفاده از ابزارهای سنجش نقاط شکست میتواند از وقوع چنین حوادثی جلوگیری کند.
برای رفع این نشت، هندلر بازنویسی شد تا بین انواع خطا تمایز قائل شود. کد اصلاحشده اکنون از لاگهای ساختاریافته برای ثبت eventId ، customerId ، پیام خطا، اثر خطا و برچسب زمانی ISO استفاده میکند:
- 503 Service Unavailable: برای انواع خطاهای موقت (TransientError) استفاده میشود تا به ارائهدهنده بگوید عملیات را بعداً تکرار کند.
- 400 Bad Request: برای شکستهای دائمی استفاده میشود تا به ارائهدهنده اعلام کند تلاش مجدد بیفایده است و باید متوقف شود.
این تغییر باعث شد نرخ خطا در داشبورد دوباره بالا برود؛ اتفاقی که برنامهنویس آن را جشن گرفت، چون تنها خطاهایی که قابل دیدن هستند، قابل اصلاحاند.
این حادثه این فرض را که «مقاومسازی» توسط AI همیشه یک مزیت خالص است، تغییر میدهد. برای توسعهدهندگان، اثر مرتبه دوم این اتفاق، ایجاد یک نیاز جدید به نام «تست مسیر شکست» (failure-path testing) است. اگر مدلی ادعا میکند خطاها را متین مدیریت میکند، برنامهنویس اکنون باید ثابت کند که این شکست هنوز برای ابزارهای نظارتی قابل مشاهده است.
چکلیست پیادهسازی برای جلوگیری از این الگو
برای اجتناب از تکرار این اشتباه، توسعهدهندگان باید این گامها را دنبال کنند:
- هر بلوک catch را بخوانید: بپرسید آیا خطا لاگ میشود، دوباره پرتاب (Rethrow) میشود یا به یک پاسخ موفقیت تبدیل میشود؟
- شناسههای لاگ را تایید کنید: مطمئن شوید لاگها شامل Event ID، Customer ID و Request ID هستند.
- کدهای وضعیت را اعتبارسنجی کنید: از کد ۵۰۳ برای خطاهای قابل تکرار و ۴۰۰ برای خطاهای دائمی استفاده کنید.
- مسیر شکست را تست کنید: کد پاسخ و خروجی لاگ را در زمان کرش اعتبارسنجی کنید، نه اینکه فقط نبودِ کرش را تست کنید.
- داشبوردها را ممیزی کنید: از خود بپرسید اگر یک شکست واقعی رخ دهد، آیا بعد از استقرار کد، واقعاً در داشبورد قابل مشاهده خواهد بود؟
کد تمیزی که شکستها را پنهان میکند، اساساً خطرناکتر از کد زشتی است که آنها را آشکار میکند. درس اصلی این است که هر تغییر پیشنهادی توسط AI در مدیریت خطا، نیازمند تستی است که صراحتاً کد پاسخ و خروجی لاگ را در هنگام کرش بررسی کند.
توسعهدهندگان باید اکنون بلوکهای catch خود را ممیزی کنند تا مطمئن شوند خطاها به پاسخهای موفقیت تبدیل نمیشوند. میتوانید با این سوال شروع کنید: «اگر دیتابیس اصلی من امروز آفلاین شود، آیا داشبوردهای نظارتی من واقعاً هشداری صادر میکنند؟»
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو