نحوه مدیریت استثناها (Exceptions) در #C

نحوه مدیریت استثناها (Exceptions) در #C

فرآیند مدیریت استثنا (Exception handling)، بخش حیاتی هر برنامه نرم‌افزاری است، چراکه شما هیچ‌وقت نمی‌خواهید کاربرانتان شاهد رخ دادن خطا یا از کار افتادن اپلیکیشن شما باشند. فریمورک NET. چندین راه مختلف برای یافتن و مشاهده استثناهای مدیریت‌نشده (unhandled exception) ارائه می‌دهد که می‌توان در #C هم از آنها استفاده کرد. در این مقاله به نحوه پیدا کردن و مدیریت تمام استثناها در اپلیکیشن‌های تحت #C می‌پردازیم. 

مدیریت First Chance Exceptions

اگر می‌خواهید تک تک استثناهای موجود در برنامه خود را پیدا و مدیریت کنید، باید درباره‌ی "First Chance Exceptions" کمی اطلاعات داشته باشید. زمانی که یک استثنا را بدون هیچ بلوک catch ای که آن را مدیریت کند، throw می‌کنید، به طور عمد باعث رخ دادن آن استثنا یا فعال کردن first chance exception می‌شوید. فعال کردن first chance exception ها در هر حالتی debugger را متوقف می‌کند. هنگامی که یک برنامه در حال debug شدن است، debugger هر زمان که یک استثنا رخ دهد از آن مطلع می‌شود و بعد تصمیم می‌گیرد که چگونه آن را به بهترین شکل مدیریت کند. در این مرحله دو سناریو وجود دارد:

  1. استثنا به برنامه منتقل می‌شود و مطابق با آن مدیریت می‌شود.
  2. وارد debug mode می‌شود و برنامه را به حالت تعلیق در می‌آورد.

در اینجا first chance exception اولین استثنایی است که به debugger منتقل می‌شود و به برنامه بازگردانده نمی‌شود.

با استفاده از فریمورک NET. می‌توانید تمام استثناهای موجود در کد برنامه خود را شناسایی کنید؛ حتی استثناهایی که در اعماق کد شما وجود دارند و هرگز در هیچ کجا ظاهر نمی‌شوند. اگرچه از نظر فنی نمی‌توانید همه‌ی استثناها را در کد #C خود «catch» کنید، اما می‌توانید از کتابخانه‌های ثبت رویداد NET Framework. استفاده کنید تا بتوانید آنها را ثبت (log) کنید. ثبت و پیداکردن این استثناها، مناسب‌ترین راه برای بهبود عملکرد برنامه و حذف رفتارهای ناخوشایند آن است.

همچنین توجه داشته باشید که استثناهای First Chance ممکن است به صورت گاه و بی‌گاه رخ بدهند و اختلال‌های کوچک زیادی ایجاد کنند. برای مثال بعضی استثناها به این دلیل رخ می‌دهند که انتظار رخ دادن آنها وجود دارد، یا ممکن است شبیه هشدارهای مختلفی که هنگام راه‌اندازی یک برنامه نشان داده می‌شوند، ظاهر شوند. پس اگر زمان مدیریت استثناها در #C با تعدادی از آنها روبه‌رو شدید، نگران نباشید.

با استفاده از قابلیت ثبت رویداد فریمورک NET. می‌توانید هر استثنا را به طور دقیق log کنید تا مشکلات عجیب و غریب و پنهان در برنامه خود را شناسایی کنید. البته به دلیل حجم زیاد اختلال‌ها و داده‌هایی که از طریق این روش ایجاد می‌شود، استفاده از آن در درازمدت و روی اپلیکیشن‌هایی که در مرحله ی Production یا استفاده توسط کاربرنهایی هستند، توصیه نمی‌شود. پس بهتر است در طول مرحله توسعه برنامه یا هنگام یافتن مشکلات آزاردهنده در مرحله ی Production، از این روش استفاده کنید. کنترل‌کننده رویداد (event handler) زیر را باید در شروع برنامه خود در فایل Program.cs، Startup.cs یا Global.asax قرار دهید.

AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
        Debug.WriteLine(eventArgs.Exception.ToString());
};

مدیریت استثناها در ASP.NET 

 زمانی که یک استثنا در ApiController اپلیکیشن‌های تحت وب ASP.NET رخ می دهد، کاربران شروع به دریافت خطاهای داخلی سرور (500 Internal Server Error) می‌کنند. مدیریت این استثناها به این  بستگی دارد که شما از کدام یک از فریمورک‌های ASP.NET یا ASP.NET Core استفاده می‌کنید. برای شروع این کار، حتماً گزینه خطاهای شخصی سازی شده (custom errors) را در فایل web.config خود فعال کنید تا کاربران شما هیچ‌وقت صفحات استثناها را نبینند.

از  رویداد Application_Error در فایل Global.asax استفاده کنید

اگر برنامه شما دارای فایل Global.asax است، باید با استفاده از آن رویدادهایی را برای استثناهای مدیریت‌نشده بنویسید. فایل Global.asax یک فایل اختیاری در ASP.NET است که برای مدیریت رویدادهای اپلیکیشن سطح بالاتر مانند Application_Start ، Application_End ، Session_Start ، Session_End و... استفاده می‌شود و با عنوان ASP.NET Application File هم شناخته می‌شود. این فایل در دایرکتوری root تمام اپلیکیشن‌های مبتنی بر ASP.NET قرار دارد. Global.asax شامل یک کلاس است که اپلیکیشن شما را به شکل کلی نشان می‌دهد. در زمان اجرای اپلیکیشن، این فایل در یک کلاس NET Framework. تجزیه و کامپایل می‌شود که این کلاس به صورت پویا ایجاد شده و از کلاس اصلی HttpApplication مشتق می‌شود.

 توجه کنید در صورتی که از فریمورک‌ها، پلتفرم‌ها و معماری‌های برنامه‌نویسی دیگری مثل MVC ،Web API ،Nancy ،SerivceStack ،WCF و... استفاده می‌کنید، ممکن است همه استثناها در بخش مدیریت خطای فایل Global.asax شما نشان داده نشوند. این وب فریمورک‌های خاص همچنین ممکن است مکانیزم‌های مدیریت خطای خاص خود را داشته باشند که در ادامه مطلب به آنها می‌پردازیم. نمونه کد استفاده از رویداد مدیریت خطا با استفاده از فایل Global.asax به شکل زیر است:

//Global.asax
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Error(object sender, EventArgs e)
    {
        Exception exception = Server.GetLastError();
        if (exception != null)
        {
            //log the error
        }
    }

    protected void Application_Start()
    {
        //may have some MVC registration stuff here or other code
    }
}

مدیریت خطای معماری MVC

با استفاده از معماری برنامه‌نویسی MVC (مخفف Model View Controller) می‌توانید برای مدیریت سفارشی خطاها در تمام Action‌ های MVC controller، از کلاس HandleErrorAttribute استفاده کنید. این معماری یک رویداد OnException در Controller خود دارد. به طور کلی، شما با مدیریت خطای سراسری برنامه از طریق فایل Global.asax می‌توانید همه استثناها را Catch کنید.

مدیریت خطای فریمورک Web API

بستر Web API قابلیت‌های پیشرفته‌تری برای رسیدگی به استثناها دارد که باید در جریان آنها باشید. این قابلیت‌ها به شرح زیرند:

  • The Exception filter: امکان سفارشی کردن مدیریت خطا برای Controller ها و اقدامات خاص را فراهم می‌کند.
  • Exception Logger: فرآیند ثبت وقایع تمام استثناهای مدیریت‌نشده را فعال می‌کند.
  • Exception handler: یک مدیریت‌کننده سراسری برای سفارشی‌سازی پاسخ‌های دریافتی از سمت APIای که فراخوانده‌شده است.

نمونه استفاده از ثبت کننده ی استثناهای مدیریت‌نشده:

public class UnhandledExceptionLogger : ExceptionLogger 
{
    public override void Log(ExceptionLoggerContext context)
    {
        var log = context.Exception.ToString();
        //Write the exception to your logs
    }
}

سپس باید ثبت کننده‌ی استثنای خود را به عنوان بخشی از فرآیند نصب و راه‌اندازی Web API ثبت کنید:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
       //Register it here
       config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger());

        // Web API routes
       config.MapHttpAttributeRoutes();

       config.Routes.MapHttpRoute(
           name: "DefaultApi",
           routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

مدیریت خطای پلتفرم WCF

پلتفرم WCF (مخفف Windows Communication Foundation) هم مانند Web API، گزینه‌های زیادی برای سفارشی‌سازی مدیریت استثناها دارد. اگر از بستر WCF استفاده می‌کنید، باید یک IServiceBehavior و IErrorHandler راه‌اندازی کنید تا همه استثناها را به درستی دریافت کنید. 

مدیریت خطای ASP.NET Core

چیزهای زیادی در بستر ASP.NET Core  تغییر کرده است. مثلاً برای جمع‌آوری استثناهای مدیریت‌نشده در ASP.NET Core، از کلاس ExceptionFilterAttribute استفاده می‌شود. این کلاس را می‌توانید یک فیلتر سراسری محسوب کنید که به عنوان یک مدیریت‌کننده استثنای سراسری عمل می‌کند. راه دیگر مدیریت خطا در ASP.NET Core استفاده از یک میان‌افزار (Middleware) سفارشی است که فقط برای catch کردن استثناهای مدیریت‌نشده طراحی شده است:

public class ErrorHandlingFilter : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        var exception = context.Exception;
        //log your exception here

       context.ExceptionHandled = true; //optional 
    }
}

همچنین باید فیلتر خود را به عنوان بخشی از Startup code در آن ثبت کنید:

//in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
   services.AddMvc(options =>
    {
       options.Filters.Add(new ErrorHandlingFilter());
    });
}

رویدادهای استثنای NET Framework.

فریمورک NET. چند رویداد برای catch کردن استثناهای مدیریت‌نشده ارائه می‌دهد. وقتی برنامه شما راه‌اندازی می‌شود، باید فقط یک بار این رویدادها را در کد خود ثبت کنید. در ASP.NET، رویدادها را باید در کلاس Startup یا فایل Global.asax بنویسید. اما در اپلیکیشن‌های ویندوز، می‌توانید این رویدادها را در چند خط اول کد در متد ()Main بنویسید.

static void Main(string[] args)
{
 Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
 AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
}

static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
  // Log the exception, display it, etc
 Debug.WriteLine(e.Exception.Message);
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
  // Log the exception, display it, etc
 Debug.WriteLine((e.ExceptionObject as Exception).Message);
}

همه‌ی استثناهای #C را با ابزار Retrace مدیریت کنید.

یکی از ویژگی‌های بزرگ ابزار Retrace، قابلیت نظارت بر خطاهاست. Retrace یک APM یا ابزار مدیریت عملکرد نرم‌افزار مبتنی بر cloud است. این ابزار می‌تواند همه‌ی استثناهای فریمورک NET. که در برنامه شما اتفاق می‌افتد را به‌طور خودکار و بدون تغییر کد آن، جمع‌آوری کند. و منظور از استثناها، تمام انواع استثناهای مدیریت‌نشده، throw شده و first chance است. نکته مثبت دیگر ابزار retrace این است که با انواع اپلیکیشن‌های ASP.NET مانند MVC ، WCF ، Web API ، .NET Core و... کار می‌کند.

Retrace همچنین گزارش‌های کاملی از همه‌ی استثناهای برنامه شما ارائه می‌دهد. حتی می‌توانید هشدارهایی را برای زمانی که تعداد استثناهای برنامه خیلی بالاست یا زمانی که یک استثنا جدید پیدا می‌شود، روی آن تنظیم کنید. Retrace سه حالت برای مدیریت استثناها ارائه می‌دهد:

  • No exceptions
  • Unhandled exceptions only
  • All exceptions thrown- از این حالت برای catch کردن همه استثناها استفاده می‌شود.

استثناها را در Windows Event Viewer مشاهده کنید

اگر برنامه شما حاوی استثناهای مدیریت‌نشده باشد، ممکن است این استثناها در قسمت Windows Event Viewer (نمایشگر رویداد ویندوز) در دسته بندی «Application» ثبت شوند. اگر بعضی مواقع نمی‌توانید بفهمید که چرا برنامه شما به طور ناگهانی crash می کند، استفاده از این ابزار ویندوز می‌تواند به شما کمک کند که اسثتثناها را پیدا کنید.

Windows Event Viewer ممکن است دو ورودی مختلف را برای یک استثنا ثبت کند؛ یکی با خطای NET Runtime. و دیگری خطای عمومی‌تر برنامه ویندوز (Windows Application Error).

نمونه خطای NET Runtime. :

Application: Log4netTutorial.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IndexOutOfRangeException
   at Log4netTutorial.Program.Main(System.String[])

نمونه خطای Application Error:

Faulting application name: Log4netTutorial.exe, version: 1.0.0.0, time stamp: 0x58f0ea6b
Faulting module name: KERNELBASE.dll, version: 10.0.14393.953, time stamp: 0x58ba586d
Exception code: 0xe0434352
Fault offset: 0x000da882
Faulting process id: 0x4c94
Faulting application start time: 0x01d2b533b3d60c50
Faulting application path: C:UsersmattDocumentsVisual Studio 2015ProjectsLog4netTutorialbinDebugLog4netTutorial.exe
Faulting module path: C:WINDOWSSystem32KERNELBASE.dll
Report Id: 86c5f5b9-9d0f-4fc4-a860-9457b90c2068
Faulting package full name: 
Faulting package-relative application ID: 

کلام آخر

آگاهی از ابزارها و روش‌های logging و مدیریت استثناها، برای هر نرم‌افزاری موضوعی حیاتی است. فرآیند ثبت وقایع معمولاً چشم و گوش برنامه‌نویس محسوب می‌شود. ما در این مقاله چند روش برای یافتن و مدیریت همه استثناها در #C را بررسی کردیم، اما برای حرفه‌ای شدن در این زمینه نیاز است تا هر یک از این تکنیک‌ها را به‌طور جزئی‌تر بررسی و تمرین کنید.

 

پیشنهادات بیشتر سکان بلاگ برای شما