مدیریت خطا: کدی که وقتی میشکند میگوید چرا

سه ساعت از یک شب کاری صرف پیدا کردن این میشود که چرا سفارش ثبت نشده. در پایان معلوم میشود سرویس پیامک تایماوت خورده، خطا در جایی گرفته شده، لاگ نوشته «خطا در ثبت سفارش»، و تراکنش نیمهکاره رها شده.
هیچکدام از این چهار اتفاق باگ منطقی نیست. همهشان تصمیم دربارهٔ مدیریت خطا هستند که کسی آگاهانه نگرفته.
و این الگو تکرارشونده است: در سامانههای سازمانی، سهم قابلتوجهی از زمان عیبیابی صرف کد خطا میشود، نه صرف کد اصلی.
تفکیکی که بقیهٔ تصمیمها از آن میآید
هر خطا یکی از این سه است، و جواب هر کدام متفاوت است:
موقت. سرویس شلوغ است، شبکه لحظهای قطع شده، پایگاه داده اتصال آزاد ندارد. اگر ده ثانیه بعد دوباره امتحان کنید احتمالاً جواب میدهد. اینها را تلاش مجدد کنید.
دائمی. ورودی غلط است، مجوز ندارید، رکورد وجود ندارد. تلاش مجدد فقط بار اضافه میسازد. اینها را سریع رد کنید.
نامعلوم. جواب نیامده و نمیدانید کار انجام شده یا نه. بدترین حالت و بیشترین بیتوجهی. درخواست پرداختی که تایماوت خورده، ممکن است پول را کم کرده باشد.
تیمی که این سه را از هم جدا نکرده، یا همهچیز را تلاش مجدد میکند (و در قطعی سرویس بیرونی، خودش را هم میخواباند) یا هیچچیز را (و در یک نوسان یکثانیهای شبکه، سفارش مشتری را از دست میدهد).
تلاش مجدد، درست انجامشده
سه شرط، و هر سه لازماند:
فاصلهٔ فزاینده با پراکندگی تصادفی. تلاش مجدد فوری، سرویسی را که تازه زیر فشار است بیشتر میخواباند. فاصله باید دو برابر شود، و یک مقدار تصادفی هم داشته باشد — وگرنه هزار کلاینت همزمان دوباره امتحان میکنند و موج دوم از موج اول بدتر است.
سقف. سه یا چهار تلاش. بعد از آن، شکست را بپذیرید و اعلامش کنید.
عملیات باید تکرارپذیر باشد. مهمترین شرط و بیشترین غفلت. اگر «ثبت سفارش» دوبار اجرا شود و دو سفارش بسازد، تلاش مجدد یک باگ است نه یک راهحل. راه استاندارد، کلید یکتای درخواست است: کلاینت شناسهای میفرستد و سرور اگر همان شناسه را قبلاً دیده، نتیجهٔ قبلی را برمیگرداند.
هر جا پول یا موجودی جابهجا میشود، این شرط قابل معامله نیست. در تسویه و اعتبار همین منطق در سطح کسبوکار مطرح است.
و یک لایهٔ بالاتر: اگر سرویسی مکرر شکست میخورد، قطعکننده بگذارید — بعد از چند شکست پیاپی، مدتی اصلاً سراغش نروید. این تفاوت بین «یک سرویس بیرونی خراب است» و «کل سامانهٔ ما خوابید» است.
مرزها: کجا خطا گرفته شود
قاعدهای که بیشترین اثر را دارد: خطا را جایی بگیرید که میتوانید دربارهٔ آن تصمیمی بگیرید.
گرفتن خطا در لایهٔ پایین، لاگ کردن یک جملهٔ کلی و برگرداندن مقدار خالی، بدترین الگویی است که در کد سازمانی میبینیم. اطلاعات از بین میرود، و لایهٔ بالا فکر میکند همهچیز درست پیش رفته.
سه مرز طبیعی:
ورودی سامانه — اعتبارسنجی، و برگرداندن خطای روشن به کاربر.
مرز فراخوانی بیرونی — تصمیم دربارهٔ تلاش مجدد و قطعکننده، همانجا.
مرز تراکنش — یا همهچیز انجام میشود یا هیچچیز. اینجا مهمترین سؤال این است: چه کارهایی داخل تراکنشاند؟ فرستادن پیامک و فراخوانی سرویس بیرونی نباید داخل تراکنش پایگاه داده باشند — چون قابل بازگشت نیستند و تراکنش را طولانی میکنند. الگوی درست، ثبت کار در صف و اجرای بعد از تثبیت تراکنش است؛ ابزارش کارهای پسزمینه و صف پیام است.
دو پیام برای دو مخاطب
خطا دو مخاطب دارد و یک متن نمیتواند به هر دو خدمت کند.
برای کاربر: چه شد، و حالا چه کند. «خطای سیستمی رخ داد» هیچکدام را نمیگوید. «ارتباط با درگاه پرداخت برقرار نشد. مبلغی کسر نشده — چند دقیقه دیگر دوباره تلاش کنید» هر دو را میگوید.
سه قاعده: بگویید داده از دست رفت یا نه؛ بگویید قدم بعدی چیست؛ جزئیات فنی ندهید — نام جدول و مسیر فایل در پیام خطا، هم بیفایده است هم نشت اطلاعات.
و یک شناسهٔ کوتاه بدهید که کاربر بتواند به پشتیبانی بگوید. همان شناسه در لاگ هم هست، و رسیدگی به گزارش را از ساعت به دقیقه میرساند.
برای لاگ: همهچیز. خطای اصلی با ردپای کامل، مقادیری که به آن رسیدند، شناسهٔ درخواست، شناسهٔ کاربر — و نه رمز و توکن و شمارهٔ کارت. لاگی که داده حساس دارد، خودش یک مسئلهٔ امنیتی است.
نکتهٔ مهم: وقتی خطا را در لایهٔ بالاتر دوباره پرتاب میکنید، خطای اصلی را نگه دارید. زنجیرهٔ علت، همان چیزی است که تفاوت پنج دقیقه و سه ساعت را میسازد.
خرابی جزئی: مهمترین تصمیم طراحی
سؤالی که باید در طراحی هر قابلیت پرسیده شود: اگر این بخش کار نکند، بقیه چه میشود؟
اگر سرویس پیشنهاد محصول جواب ندهد، صفحه باید بدون پیشنهادها بیاید، نه اینکه خطا بدهد. اگر سرویس امتیازدهی خواب باشد، سفارش باید ثبت شود و امتیاز بعداً حساب شود.
این تصمیم را باید قابلیت به قابلیت گرفت و نوشت: کدام وابستگی حیاتی است و کدام تزیینی. سامانهای که همهٔ وابستگیهایش حیاتیاند، سامانهای است که دسترسپذیریاش حاصلضرب دسترسپذیری همهٔ آنهاست — عددی که خیلی زود کوچک میشود. بحث کاملترش در دسترسپذیری بالا.
و خطاهایی که کسی نمیبیند
خطایی که فقط لاگ میشود، خطایی است که کسی نمیبیند. نرخ خطا باید سنجهای باشد که هشدار دارد — پایش و لاگ دربارهٔ همین است، و SLO دربارهٔ اینکه چه نرخی قابل قبول است.
عددی که در عمل بیشتر از خود نرخ خطا اهمیت دارد: چقدر طول کشید تا فهمیدیم. سامانهای که ده دقیقه خطا میداد و در دقیقهٔ اول هشدار داد، وضعیت سالمتری دارد از سامانهای که دو ساعت خطا میداد و مشتری خبر داد.