رسیدگی به خطاها (قسمت اول)

مطلبی که می‌خوانید ترجمه‌ی قسمت ۷ از رادیو مهندسی نرم‌افزار است. رادیو مهندسی نرم‌افزار هر یکی دو هفته یک بار مصاحبه‌ای درباره‌ی یکی از موضوعات حوزه‌ی مهندسی نرم‌افزار با افراد خبره و با تجربه در موضوع مورد بحث ترتیب می‌دهد.

در این اپیزود، مارکوس ولتر با مارکوس آرنو در مورد خطاها و رسیدگی به آنها در سطح معماری صحبت می‌کنند. آن‌ها در مورد انواع خطاها، گروه‌های افرادی که باید آن‌ها را بشناسند، و راهکارهای سطح بالای اثبات‌شده در این مورد بحث می‌کنند. در اپیزود بعدی جنبه‌های فنی‌تر رسیدگی به خطا از قبیل اصطلاحاتی که در کار با خطاها وجود دارد و مقایسه استثناهای چک‌شده (Checked Exception) و غیر چک‌شده (Unchecked Exception) بررسی می‌شود.

خطا چیست؟ از چه تشکیل یافته؟ چگونه باید آن را رسیدگی (Handle) کنیم؟

ابتدا باید بگویم که رسیدگی کردن خطا بخش مهمی از تولید سیستم است که عموماً نادیده گرفته می‌شود. آمار نشان می‌دهد که ۸۰٪ کد برنامه در ارتباط با رسیدگی خطا و حالت‌های استثنا (Exception) است. عموماً نوشتن کد برای آنچه سیستم باید در شرایط خوب انجام دهد، ساده است و شرایط دیگر مربوط به خطا و حالت‌های استثنا و جاهایی که کار بدرستی پیش نمی‌رود، معمولاً بخوبی مشخص نمی‌شوند و نوشتن آنها بسیار سخت‌تر است.

خطا چیست؟ خطا هر چیزی است که جلوی کار مورد انتظار سیستم را بگیرد. ۳ نوع مختلف خطا وجود دارد. اولین خطا که ما برنامه‌نویسان کاملاً با آن آشنا هستیم باگ ها هستند. اگر چیزی در برنامه بنویسیم که مطابق با آنچه می‌بایست نباشد، باگ داریم مثلاً اگر فراموش کنیم که شرایط مرزی را چک کنیم. این مورد، یقیناً مانع کار مورد انتظار سیستم می‌شود. نوع دیگر خطا که کاملاً با باگ متفاوت است، ورودی‌های غیرمجاز است. اگر در برنامه دیالوگی داشته باشید که فیلدی داشته باشد که قرار باشد در آن یک تاریخ وارد شود و به جای آن، یک متن وارد شود، حتما مانع عملکرد مورد انتظار سیستم می‌شود. کاری که می‌بایست انجام دهید این است که ورودی‌ها را پردازش کرده و آن را چک کنید. نوع دیگر خطاها به این صورت هستند که زیرساخت‌‌ها کار نمی‌کنند. بعنوان مثال، دسترسی نوشتن به فولدری که نیاز دارید در آن بنویسید را ندارید یا اینکه شبکه کار نمی‌کند و مواردی از این قبیل.

شما گفتید که ۸۰٪ کد برنامه‌ها مربوط به رسیدگی خطا است. سئوالی که پیش می‌آید این است که آیا این موضوع در طول زمان تغییر کرده است؟ آیا زبان‌‌های برنامه‌نویسی بهتر، این مقدار را کاهش داده است یا این آمار همواره پایدار بوده است؟

من از تحقیقاتی که این موضوع را در طی زمان، بررسی کرده باشد خبر ندارم. اگر در طی زمان این مورد کمتر شده باشد، من متعجب خواهم شد زیرا اینها (خطاها) چیزهایی است که نیاز داریم به آنها فکر کنیم. منظورم این است که مثلاً رسیدگی به شرایط مرزی یا بررسی زیرساخت‌ها کارهایی است که همواره نیاز است انجام شود.

ممکن است برخی از این امور در طی زمان به کتابخانه‌ها و فریم‌ورک‌ها منتقل شده باشند، اما هنوز سهم زیادی باید درنظر گرفته شوند.

در ارتباط با ۳ نوع خطایی که معرفی کردید آیا نیاز است که آنها را به شکل‌های متفاوت رسیدگی کنیم؟ آیا مهم است که بتوانیم آنها را از هم تشخیص دهیم؟ چرا اینها را همان ابتدای بحث مطرح کردید؟

اول از همه باگ ها را داریم. باگ ها را نمی‌توان واقعاً رسیدگی کرد! منظورم این است که اگر باگی وجود داشته باشد نمی‌توانید با اقدامات امنیتی اطمینان یابید که به چیزی جز یک core dump یا یک پیام غیر قابل فهم می‌رسید. در مورد باگ‌ها، فقط می‌توانیم بصورت برازند‌ه‌ای رفتار سیستم را در یک خروجی مربوط به دیباگ گزارش دهیم. البته قطعاً این کاری نیست که می‌خواهیم با ورودی‌های غیرمجاز انجام دهیم. از آن زمانی که انتظار می‌رفت اگر کاربر در یک فیلد تاریخ، یک متن را وارد کرد، سیستم از کار بیافتد، مدت‌ها گذشته است. در این موارد کاربران انتظار خطا ندارند. آنها انتظار دارند که فقط پیغامی بیاید که ورودی نامعتبر است و لطفاً مجدد وارد کنید و مواردی از این قبیل.

خطاهای مرتبط با زیرساخت‌ها، هم چیزهایی هستند که کاربران ممکن است نتوانند در مورد آنها همکاری کنند. ممکن است نیاز باشد نوعی تلاش مجدد انجام دهید.

ممکن است کاربران نتوانند آنها را برطرف کنند اما بخواهید به آنها اطلاع دهید که مثلاً به اینترنت وصل نشده‌اید و بنابراین نمی‌توانید با کسی صحبت کنید.

بله، دقیقاً. این نوع دیگری از اطلاع‌رسانی‌هایی است که کاربر دریافت می‌کند.

چه استراتژی‌هایی برای رسیدگی خطا وجود دارد؟

اگر بتوانیم، خیلی ساده؛ به کاربرانمان اهمیت نمی‌دهیم. گاهی مواقع، به نظر می‌رسد در برخی سیستم‌های بزرگ همین روش انجام می‌شود. شوخی کردم :-) انواع مختلفی از افراد وجود دارند که به رسیدگی خطا، به گزارش خطا و مطلع شدن از آن علاقه‌مند هستند. رسیدگی خطا باید تا جایی که می‌شود بصورت برازنده‌ای انجام شود. سیستم باید هر کاری که می‌تواند انجام دهد تا وظیفه‌اش را انجام دهد اما اگر نمی‌تواند و واقعاً کار سختی است باید نیازمندی‌های گروه‌های مختلف در نظر گرفته شود.

گروه اول کاربران نهایی هستند: کسانی که از سیستم‌های نرم‌افزاری استفاده می‌کنند. وقتی داریم خطا را رسیدگی می‌کنیم، نیاز این گروه باید همواره در بالای لیست اولویت قرار گیرد. ما می‌خواهیم به آنها بگوییم که چه اتفاقی افتاده اما به زبانی که برایشان قابل درک باشد. اگر خطا در ورودی باشد می‌خواهیم به آنها بگوییم اما اگر خطا به علت باگ هم باشد باز هم می‌خواهیم این را بگوییم. اگر خطایی بعلت باگ رخ داده است مناسب نیست که فقط پنجره بسته شود و سیستم برود یا حتی اینکه پنجره ارسال فیدبک را باز کنیم. بهتر است که بگوییم یک خطای داخلی رخ داده است. ما خیلی متأسفیم که این اتفاق رخ داده است. ما بر روی آن کار می‌کنیم و لطفاً برنامه را از نو اجرا کنید. یا شاید بهتر باشد این امکان را بدهیم که کارشان را ذخیره کنند مثلاً بگوییم که یک نسخه کپی موقتی از کارتان در آدرس فلان ذخیره شده است. نیازهای کاربران نهایی باید مهمترین نیازمندی‌ها باشد.

فقط بعنوان یک نظر در این مورد: به نظر من در برخی موارد مثلاً در مورد سیستم‌های تعبیه شده بهتر است که در هنگام وقوع خطا، کار را متوقف کنیم تا اینکه یک کار غلط انجام دهیم. مطمئناً در برخی شرایط، یک استراتژی رسیدگی خطا، توقف برنامه است.

شما قطعاً درست می‌گویید که بهتر است کاری نکنیم تا اینکه بخواهیم کار غلطی بکنیم ولی عموماً این شرایط مربوط به سیستم‌های غیرتعاملی و سیستم‌های تعبیه شده است.

عموماً این سیستم‌ها نوعی حالت امن از خطا (Fail Safe) هم دارند که اگر بخشی از سیستم خراب شود، همان بخش، از کار می‌افتد اما بخش‌های دیگر سیستم همچنان در یک حالت عملکرد سطح پایین‌تر به کارشان ادامه می‌دهند. سیستم همچنان کار می‌کند، هواپیما به پروازش ادامه می‌دهد! اما نمی‌توانید همه کارهای قبلی را انجام دهید.

دومین گروه که به رسیدگی خطا علاقه‌مند هستند، مدیرسیستم‌‌ها (Administrator) هستند خصوصاً در سیستم‌های تعبیه شده یا غیرتعاملی، اینها کسانی هستند که باید از خطا آگاه شوند. آنها خیلی راحت فراموش می‌شوند. منظورم این است که اگر سیستمی میزان کیفیت خدمتش (Quality of Service) کاهش یابد یا اینکه سیستمی مشکل خطای شبکه دارد، خیلی مهم است که مدیرسیستم مطلع شود تا این امکان فراهم شود که تشخیص دهد چه مشکلی پیش آمده است مثلاً با کار کردن با نرم‌افزار مدیریت سیستم یا با بررسی لاگ‌هایی که می‌توان بصورت خودکار آنها را مرور کرد یا هر روش دیگری. بهرحال خیلی مهم است که چک کنیم بوسیله روش‌های اطلاع‌رسانی، مدیر‌سیستم‌ها از خطاها مطلع شوند. این گروه، افرادی هستند که خیلی مواقع به آنها بی‌توجهی می‌شود.

و گروه سومی که نیاز است مورد توجه قرار گیرند و از آنچه رخ داده باخبر شوند برنامه‌نویسان هستند. واضح است که این گروه باید در سطوح کاملاً محلی و تکنیکی اطلاع یابند. هر برنامه‌نویسی حتی شده با نوشتن در خروجی استاندارد (Standard Out)، لاگ‌هایی به منظور دیباگ کردن سیستم نوشته است. اما خیلی مهم است که در سطح معماری این موضوع جدی گرفته شود و برای آن برنامه‌ریزی شود زیرا به سادگی ممکن است خطایی در بخشی از سیستم رخ دهد و تنها به صورت محلی ثبت شود و تصویر کلی از دست برود و اطلاعات مربوط به دیگر بخش‌های سیستم از دست رفته باشد یا جزییاتی که برای دیباگ کردن مورد نیاز است فراهم نباشد.

رسیدگی خطا، وظیفه چه کسی است؟ همانطور که شما اشاره کردید، گروه‌های مختلفی وجود دارند. چه کسی آنها را رسیدگی می‌کند و چرا؟

دو دیدگاه در مورد استراتژی‌های رسیدگی خطا وجود دارد. البته ابتدای کار بگذارید بگویم که من دارم فرض می‌کنم که در زبان برنامه‌نویسی، امکان پَرت کردن استثنا (Exception) را داریم زیرا اگر بخواهیم فقط با بازگشت (Return) و فراخوانی (Call) و علت (Cause) کار کنیم در آن صورت مجبور خواهیم بود که خطاها را خیلی محلی و جزیی رسیدگی کنیم. پس بیایید فرض کنیم که از امکان رسیدگی به استثنا (Exception Handling) استفاده می‌کنیم. در این صورت، همچنان به دو روش می‌توانیم کار کنیم. روش اول این است که هر بخشی از کد مسئول انجام کار خودش است و اگر نتواند کارش را انجام دهد، یک استثنا، پَرت می‌کند. اینکار خیلی مرتبط با طراحی از طریق قرارداد (Design By Contract) است که برتراند مایر ارائه کرده است.

هر قسمت از نرم‌افزار مثلاً یک کلاس یا یک متد، یک قرارداد ارائه می‌کند که اگر ورودی صحیح باشد، خروجی معینی را تولید می‌کند و به عنوان بخشی از قرارداد مشخص می‌کند برای اینکه بگوید کاری را نمی‌تواند انجام دهد، در چه شرایطی چه نوع استثنایی را پَرت می‌کند. هر قسمتی برای اینکه بتواند مشخصاً قراردادی که به بقیه دنیا ارائه کرده است را برآورده کند نیاز خواهد داشت که استثناها را بصورت محلی رسیدگی کند.

یعنی اگر به عنوان مثال عملگری داشته باشید، مشخص می‌کنید که این عملگر استثناهایی از نوع A و B و C را پَرت می‌کند و عملگر می‌بایست همه مشکلات داخلی را رسیدگی کند و فقط این نوع استثناها را گزارش دهد. آیا منظورتان این است؟

بله، این طراحی از طریق قرارداد است: روش محلی رسیدگی به استثناها.

البته طراحی از طریق قرارداد فراتر از این می‌رود جایی که به صورت رسمی، عبارت‌های بولی (Boolean) می‌آورید که شرایط اولیه را بیان می‌کنند. احتمالاً در اپیزود‌های بعدی به آن می‌پردازیم.

بله، قطعاً. باید در اپیزودهای بعدی جزییات بیشتری از آن را بررسی کنیم. اما به رسیدگی به استثناها برگردیم. اگر شما دیدگاه محلی به آن داشته باشید و آن را به صورت محلی انجام دهید این خطر وجود دارد که در چنین کدی، همه‌جا، بلاک‌های دریافت (Catch) ایجاد شود. یعنی دوسوم کد، بلاک‌های دریافت شود که واقعاً نوع جدیدی از استثنا ایجاد نمی‌کنند بلکه آنها را بدون اینکه واقعاً رسیدگی کنند، مجدداً بسته‌بندی می‌کنند. این باعث می‌شود که خواندن کد خیلی خیلی سخت شود و باعث می‌شود یک خط کد، به ۳۰-۴۰ خط کد افزایش یابد و این خصوصاً برای قابلیت خوانایی و قابلیت نگهداری کد مشکل ایجاد می‌کند و واقعاً ارزشی به سیستم نمی‌افزاید زیرا تمام آنچه انجام شده بسته‌بندی دوباره آنها (Re-wrap) بوده است. به مرور، من دیدگاه دیگری نسبت به رسیدگی استثناها پیدا کردم و آن تصویر کلی این است که بگذارم همه استثناها در پشته فراخوانی‌ها (Call Stack) بالا بروند و اصلاً آنها را به صورت محلی رسیدگی نکنیم.

بیایید نگاهی به انواع خطاها بیاندازیم. اگر خطا از نوع باگ باشد هیچ مزیتی در بسته‌بندی کردن باگ در استثناهای (Exception) خاص مربوط به برنامه ما نیست. دوباره و دوباره بسته‌بندی کردن آن در حین عبور از پشته فراخوانی‌ها (Call Stack) مزیتی ندارد. باگ، باگ است و عموماً در یک سطح بالای کد و بالای پشته رسیدگی می‌شود و باعث می‌شود که یک پیغام بالا بیاید و طراحی از طریق قرارداد برای باگ‌ها مزیتی ندارد.

این همان چیزی است که قبلاً هم گفتید. شما نمی‌توانید انتظار بعضی خطاها را داشته باشید. نمی‌توانید خطای اشاره‌گر خالی (Null Pointer) را همه جا دریافت کرده (Catch) و آن را به چیزی دیگری بسته‌بندی کنید زیرا شما انتظار آن را ندارید. اگر انتظارش را داشتید، جلوی وقوعش را می‌گرفتید. درست است؟

بله، دقیقاً. اشاره‌گر خالی یک چیز واضحی است اما به عنوان مثال دیگر، اگر نوعی مکانیزم بازتاب (Reflection) داشته باشید، هرچیزی ممکن است رخ دهد. مثلاً پیغامی که ارسال می‌کنید توسط شیء شناسایی نشود یا اینکه متد موردنظر وجود نداشته باشد. معنی ندارد که اینها را به یک نوع استثنا (Exception) دیگر بسته‌بندی کنیم. خیلی مواقع، اگر آن متد وجود ندارد، پس باگ است. در جاوا، کامپایلر شما را اجبار می‌کند که این استثناها را به‌نوعی رسیدگی کنید اما در خیلی موارد، تنها باگ است و معنی ندارد که آنها را به صورت محلی رسیدگی کنید.

مورد دیگر، ورودی غیرمجاز است. این موارد می‌بایست یکپارچه شوند. اگر دیالوگی دارید که فیلدهایش می‌توانند غیرمجاز باشند، یک حلقه حول فیلدهای ورودی وجود خواهد داشت که هرکدام از آنها را بررسی می‌کند و یک پیغام یکپارچه می‌سازد. اینجا نیز از مواردی است که نیازی به دوباره و دوباره بسته‌بندی کردن استثناها نیست.

مورد سوم خطاهای مربوط به زیرساخت‌ها است مثلاً مشکلات کار با پایگاه داده یا مشکلات کار با شبکه. در این شرایط، واقعاً مفید است که در بالاترین سطح، استثنا اصلی را همچنان داشته باشیم. اغلب، این همان موردی است که ما به آن علاقه‌مندیم. اگر یک مشکل شبکه‌ای وجود دارد، مثلاً فرض کنید که اتمام مهلت در سرویس نام (Naming Service Timeout) داریم. این واقعاً همان چیزی است که ما می‌خواهیم در سطح واسط کاربر داشته باشیم تا آن را به کاربر نمایش دهیم بنابراین نیازی نیست که ۵ لایه استثناهای دیگر خاص برنامه به دور آن بپوشانید.

یک الگوی طراحی وجود دارد که می‌گوید که استثناهای یک لایه خاص از برنامه باید فقط مفاهیم همان لایه را استفاده کنند یعنی اگر به عنوان مثال مولفه‌ای دارید که آدرس افراد را نگه می‌دارد و این مولفه داخل خودش از پایگاه داده استفاده می‌کند، این الگو می‌گوید که مولفه ذخیره‌سازی Person می‌بایست استثناهایی مثلاً از نوع‌های PersonNotFound ،PersonInvalid و PersonNotStored داشته باشد و نباید چیزهای مربوط به زیرساخت‌ها و سطوح زیرین معماری را برگرداند. شما می‌گویید این کار بی‌معنی است و باید اجازه داد آن استثناهای های سطح پایین فنی تا سطوح بالای معماری بالا بیایند.

شما اکنون دارید در مورد الگوی معماری لایه‌ای صحبت می‌کنید. الگوی معماری لایه‌ای در کتاب POSA معرفی شده است. قطعاً در مورد آن، معنی نمی‌دهد اما مرزهایی که دریافت (Catch) استثناها (Exception) و بسته‌بندی مجدد آنها، در آنجا معنی می‌دهد، عموماً مرزهای بین تیم‌ها و مرزهای بین زیرسیستم‌ها در سطوح خیلی بالا در سیستم‌های خیلی بزرگ است. در آنجا قطعاً معنی می‌دهد که آنها را دریافت و مجدداً بسته‌بندی کنیم. اگر مثلاً در سایت آمازون، نوعی خطای پایگاه داده رخ دهد برای من مهم نیست. من فقط می‌خواهم بدانم که مشکلی در پیدا کردن کتابی که دنبالش بوده‌ام رخ داده است. در چنین شرایطی، اضافه کردن این تجریدها معنی می‌دهد. اما در این سال‌ها یک گرایش وجود داشته است که از این الگو در مقیاس‌های کوچک و کوچک‌تر و کوچک‌تر حتی در جایی که یکسری کلاس‌های منفرد، نقش زیرسیستم‌ها را دارند، استفاده شود. و این واقعاً مشکل‌زا است. در داخل تیم واحدی از افراد که قرار است خطاها را بفهمند و اشکالزدایی کنند و داخل یک تیم واحد از مدیرسیستم‌ها، بسته‌بندی‌کردن مجدد خطاها معنی نمی‌دهد. ساده‌تر و بهتر است که همان اطلاعات استثنا (Exception) اصلی را داشته باشیم.

شما به این مطلب اشاره کردید که رسیدگی به خطا، چیزی نیست که بتوانید آن را کاملاً محلی انجام دهید و چیزی است که باید جزیی از معماری باشد. درست است؟

بله، دقیقاً. خیلی مهم است که رسیدگی به خطا در تمامی معماری سیستم با سبک یکسان و یکنواختی انجام شود. این که روش یکسانی برای انجام کارها وجود داشته باشد. زیرا خصوصاً مدیر‌سیستم‌ها و کاربران نهایی انتظار دارند که خطاهای مختلف به روش یکسانی نمایش داده شوند. برنامه‌نویسان این طور نیستند. خروجی‌های دیباگ را هرکس به سبک خودش می‌نویسد. این مطلب می‌تواند با مبحث طراحی برآمده از مدل (Model Driven) مرتبط باشد زیرا آن روش یکی از روش‌هایی است که معماری یکسان اجبار می‌شود که مبحثی است که در اپیزودهای گذشته در مورد آن صحبت کرده‌ایم.

اندی لانگ‌شاو هم چند الگوی خوب در مورد استراتژی‌های رسیدگی به خطاها نوشته است (برای مطالعه این مجموعه الگوها می‌توانید به این لینک و این لینک مراجعه نمایید -مترجم). شاید بتوانیم در اپیزودهای آینده او را هم برای مصاحبه بیاوریم.

بله، ایده خوبی است.

بسیار خوب، یک مطلب اصلی که من از این بحث آموختم دریافت کردن (Catch) در سطوح بالا بود. درست می‌گویم؟

بله، دقیقاً. این یکی از الگوهای موردعلاقه من از مجموعه الگوهای لانگ‌شاو است. ایده همان چیزی است که من هم‌اکنون توضیح دادم. اینکه در همه جای کد، بلاک‌های دریافت کوچکی نداشته باشیم که تلاش می‌کنند به خطاهایی رسیدگی کنند که واقعاً نمی‌توانند به آنها رسیدگی کنند و بجای آن یک بلاک دریافت در بالای پشته فراخوانی‌ها (Call Stack) داشته باشیم که همه رسیدگی به خطاها را انجام دهد و فقط زمانی که واقعاً می‌توانیم رسیدگی به خطاها را بصورت محلی انجام دهیم این کار را بکنیم. اگر لازم است که بصورت محلی، اطلاعاتی به آن بیافزاییم این کار را بکنیم اما در نهایت، رسیدگی اصلی به خطاها در این بلاک دریافت سطح بالا انجام شود.

ولی این تنها برای خطاهایی مفید است که نمی‌توان برای آنها تلاش مجدد داشت زیرا ممکن است بخواهیم استراتژی‌های ترمیمی (Recovery) داشته باشیم. آنجا معنی می‌دهد که دریافت (Catch) را در سطوح پایین انجام دهیم و برایش کاری کنیم.

بله، قطعاً. اگر می‌توانید تلاش مجدد داشته باشید این کار را بکنید. اگر می‌توانید بصورت محلی خطا را به طرز معنی‌داری رسیدگی کنید این کار را بکنید. اما خیلی از موارد هستند که نمی‌توان خطاها را محلی رسیدگی کرد. نکته این است که اگر چیزی هست که نمی‌شود رسیدگی کرد، اَدای آن را درنیاورید! و بعد، خوب است که یک نقطه در بالای پشته داشته باشید که همه چیز را دریافت (Catch) کند و پردازش‌ها را آنجا انجام دهد. برای برنامه‌های با واسط گرافیکی، آنجا، مکانی در نزدیکی حلقه رخدادها (Event Loop) خواهد بود و در برنامه‌های دسته‌ای (Batch) هم، احتمالاً یک حلقه اصلی سطح بالا دارید که بر روی داده‌ها، می‌چرخد که جایی خواهد بود که بلاک دریافت سطح بالا قرار خواهد گرفت.

مطلب دیگری که ارزش ذکر کردن دارد این است که رسیدگی به خطاها چیزی است که نباید بیش از حد، خودکارسازی شود. یک بحثی که همیشه در سیستم‌های توزیع‌شده مطرح بوده، این است که این سیستم‌ها تا چه میزان باید شفاف باشند. کاربران باید بدانند که فراخوانی بر روی یک شیء بر روی یک سیستم راه دور (Remote) انجام شده و به همین علت خطاهای دیگری از قبیل عدم دسترسی شبکه می‌تواند رخ دهد.

آنچه شما می‌گویید این است که نباید تلاش کنیم آن را مخفی کنیم. ما نمی‌خواهیم افراد فراموش کنند که بر روی یک سیستم توزیع‌شده یا سیستم دیگری با یک معماری پیچیده قرار دارند. شما می‌خواهید افراد اینها را بدانند.

بله، قطعاً. سیستم‌های توزیع‌شده‌ای که توزیع‌شدگی شفاف (Transparent Distribution) داشته باشند، وهمی بود که در صده ۱۹ به آن علاقه‌مند بودند. من فکر می‌کنم، الان این مرحله را گذرانده‌ایم.

بنابراین خطاها و استثناها در واسط گرافیکی (GUI) رسیدگی می‌شوند. درست است؟

تا حدی، این قاعده سرانگشتی خوبی است. برای خیلی از برنامه‌ها که خیلی بزرگ نباشند، همین روش کفایت می‌کند، البته اگر GUI داشته باشید. وقتی می‌خواهید از این قاعده سرانگشتی استفاده کنید البته می‌بایست توجه داشته باشید همان طور که چند بار تذکر دادیم اگر می‌توانید یک خطا را بصورت محلی رسیدگی کنید این کار را بکنید. اگر می‌توانید بعد از یک اتمام مهلت (Timeout) تلاش مجدد داشته باشید یا ترمیم داشته باشید اینکار را بکنید. گاهی مواقع بهتر است که نتیجه را حدس بزنید.

جالب شد. منظورتان از حدس زدن نتیجه چیست؟

من یکبار بر روی سیستمی کار می‌کردم که دسترس‌پذیری بالا (High Availability) ارجحیت داشت. اگر پایگاه داده در دسترس نبود یا اگر سروری، در دسترس نبود، بهتر بود که ریسک می‌کردیم تا اینکه از کار افتادن سیستم را برای کاربر مشخص کنیم. ریسک مالی، این را الزام می‌کرد.

مثلاً در شرایطی که تصمیم‌گیری‌های بله/خیر دارید. اگر برای آن محاسباتی دارید که نرخ سود را محاسبه می‌کند اما نمی‌توانید این محاسبات را انجام دهید در این صورت خیلی معنی نمی‌دهد که به کاربر پاسخ دهید: خطا، دوباره درخواست کنید.

بله، شاید برای محاسبه نرخ سود این طور باشد. در برخی سیستم‌های تعبیه شده نیز ممکن است شرایطی باشد که بهتر باشد به جای اینکه سیستم را پایین بیاوریم، حدس‌هایی بزنیم. البته شما باید بدانید که چه کار می‌کنید. باید یک منطق محکم پشت آن باشد. قطعاً شرایطی وجود دارد که حدس زدن پاسخ یا دادن پاسخ‌های قلابی بهتر از پایین آوردن سیستم است.

نکته دیگر این است که به مرز بین ماشین‌ها، از منظر مدیریت سیستم‌ها، توجه داشته باشید. مهم است که قبل از عبور از مرزها، جهت رسیدگی مدیرسیستم، کارهایی انجام شود. به عنوان مثال در بسیاری از سیستم‌های توزیع‌شده، یعنی سیستم‌های کلاینت/سرور، اگر مدیرسیستم ماشین کلاینت، با مدیرسیستم ماشین سرور متفاوت باشد، آنگاه اگر مشکلی در سرور باشد، خیلی مهم است که این مشکل قبل از ترک سرور، لاگ زده شود. زیرا اگر لاگ در ماشین کلاینت زده شود، برای افرادی که مدیریت ماشین سرور را به عهده دارند، در دسترس نخواهد بود.

این الگوی دیگری از الگوهای لانگ‌شاو است: الگوی لاگ در مرزهای سیستم. درست است؟

بله، دقیقاً. مطلب دیگر، لاگ زدن در مرز تیم‌هاست. اگر بخش‌های مختلف سرور، توسط تیم‌های مختلفی رسیدگی می‌شود، باید لاگ‌ها داخل این مرزها نگهداری شود. همه چیز مربوط به گروه هدف است. برای چه‌کسی رسیدگی خطا را انجام می‌دهیم؟ چه کسی به آن علاقه‌مند است؟ چگونه می‌توانیم اطمینان یابیم که اطلاعاتی که می‌خواهیم، به دست گروه خاصی که این کارها را برایشان انجام می‌دهیم، می‌رسد؟

بسیار خوب، گام بعدی در بحث رسیدگی به خطا این خواهد بود که به جزییات عمیق‌تر استراتژی‌ها بپردازیم. مواردی مانند اینکه چه زمانی می‌خواهید استثناها، پَرت شوند؟ چه زمانی از استثناهای چک‌نشده (Unchecked Exception) استفاده می‌کنید؟ اما اینها را برای اپیزود بعدی می‌گذاریم. درسته؟

بله، و همین طور مباحثی از این قبیل که چطور سلسله‌مراتب استثناها را بسازیم و اینکه چه مقدار منطق در آن‌ها قرار دهیم.

من فکر می‌کنم که از شما شنیدم که مقداری نسبت به این ایده که همه انواع استثناها، از نوع چک‌شده باشند انتقاد دارید زیرا این شما را مجبور می‌کند که آنها را دریافت (Catch) کنید زیرا در غیر این صورت کامپایلر ایراد می‌گیرد. اما نمی‌توانید بغیر از بسته‌بندی مجدد آن با استثناهای دیگر و دوباره پَرت کردن آنها کاری کنید. فکر می‌کنم در هنگام بحث در مورد این امور سطح پایین، عقاید غیرمتعارفی داشته باشید.

یقیناً. قطعاً یک بحث چالشی خواهیم داشت.

مطلب دیگری هست که بخواهید بگویید؟

فقط یک مطلب مهم این است که رسیدگی خطا را بصورت جدی انجام دهید. برای طراحی آن در سطح معماری، زمان بگذارید و همه کسانی که می‌بایست از خطاها مطلع شوند را درنظر بگیرید. آنها فقط برنامه‌نویسان نیستند بلکه کاربران نهایی و مدیرسیستم‌ها هم هستند.