رفتن به مطلب

مدیریت استثنا‌ها در Net.


Fahim

ارسال های توصیه شده

مقدمه

در زمان طراحی یک برنامه احتمال بروز خطا همیشه وجود دارد. خطاها به دو شکل ظاهر می‌شوند؛ در حالت اول یک ترکیب نحوی اشتباه یا فراموش کردن یک کاراکتر در پایان دستورات باعث ایجاد خطایی در زمان کامپایل برنامه می‌شود. این‌گونه خطاها به‌راحتی قابل تشخیص بوده و توسط کامپایلر گزارش داده می‌شوند. اما نوع دیگری از خطاها شکل‌های پیچیده‌تری نسبت به حالت اول دارند که به آن‌ها خطاهای زمان اجرا (runtime error) می‌گویند. این خطاها تا زمان اجرای برنامه قابل تشخیص نخواهند بود.

به‌عنوان مثال، زمانی که عملیات I/O روی فایل‌ها انجام می‌گیرد یا زمانی که یک ارتباط به پایگاه داده‌ها باید ایجاد شود، نمونه‌ای از حالت‌هایی است که ممکن است سبب بروز خطا شوند. در چنین شرایطی اگر از الگوی مناسبی استفاده نشده باشد، نتایج غیرقابل پیش‌بینی به‌وجود خواهد آمد. در این‌گونه حالات برنامه با نمایش یک پیغام خطای تولید شده توسط CLR پایان می‌پذیرد. در این میان، نوع دیگری از خطاها نیز وجود دارند که مربوط به اشیاء پویا (Dynamic) می‌شوند، به‌عنوان مثال اگر در زمان طراحی، از یک ترکیب نحوی اشتباه استفاده شود، این مورد توسط کامپایلر گزارش داده نمی‌شود و خطا خود را در زمان اجرای برنامه، نشان خواهد داد. به این دلیل مدیریت این نوع خطاها روش خاص خود را دارد.

نرم‌افزار Visual studio یکی از قوی‌ترین ابزارهایی است که برای ساخت برنامه‌های مختلف از آن استفاده می‌شود. امکاناتی که ویژوال استودیو برای ساخت برنامه‌های مختلف به کاربران ارائه می‌دهد، بسیار زیاد است. یکی از این ویژگی‌های قدرتمند، ابزارهایی است که برای Trace کردن یک برنامه وجود دارد. این ابزارها که در قالب پنجره‌هایی مانندcall stack Locals/Watch/Auto/ و با پشتیبانی ازBreakpoints همراه هستند، در منوی Debug قرار دارند. بررسی خط به خط (Step Info/Step over/Step out) یک برنامه تا قرار دادن چند متغیر یا یک عبارت در پنجره watch که در زمان اجرای برنامه می‌توانید آن‌ها را لحظه به لحظه بررسی کنید، نمونه‌ای از این توانمندی‌ها است. اما هدف این مقاله تشریح مؤلفه‌های فوق نیست، بلکه هدف، تمرکز روی مکانیزم‌هایی است که مدیریت خطاها را پیاده‌سازی می‌کنند.

 

Exception چیست؟

زمانی که یک نوع یا یک شیء را تعریف می‌کنید، این شیء می‌تواند دارای انواع مختلفی از عضوها مانند متدها، خاصیت‌ها و رویدادها باشد و هر کدام از موارد فوق می‌توانند عملیاتی را روی یک شیء انجام دهند. اما زمانی که یک عضو نتواند وظیفه خود را کامل کند، در این حالت یک Exception یا استثنا رخ می‌دهد. یک استثنا بیانگر یک حالت غیرمعمول در روند اجرای یک دستور است. این حالت بیانگر آن است که متد، فرآیند اجرای خود را به‌درستی انجام نداده، در نتیجه شی‌ءهای استثنا که توصیف‌کننده خطا هستند، ساخته و پرتاب می‌شوند (استثنا‌‌ها به‌صورت دستی نیز با استفاده از کلمه کلیدی throw قابل پرتاب شدن هستند، که در ادامه، آن‌ها را خواهید دید). به‌عنوان مثال، زمانی که عملیات I/O مانند نوشتن، باید روی یک فایل انجام شود اما فایل روی یک رسانه فقط خواندنی قرار دارد، یک استثنا یا Exception تولید می‌شود. مایکروسافت در سایت MSDN استثنا را این‌گونه توصیف می‌کند: «روشی استاندارد که net. با استفاده از آن خطاها را گزارش می‌کند.»

 

Exception Handling چیست؟

مکانیزمی که به استثنای تولید‌شده واکنش نشان می‌دهد، Exception Handling نامیده می‌شود. این مکانیزیم برای اداره کردن خطاها از بلوک‌های try/catch/finally استفاده می‌کند که نحوه استفاده و پیاده‌سازی آن‌ها را در ادامه خواهید دید. در زمان بروز خطا شیء Exception که شامل جزئیاتی در‌خصوص استثنای رخ‌داده است، پرتاب می‌شود. استثنا در زمان پرتاب، درون پشته قرار می‌گیرد. CLR وظیفه اداره کردن استثنا را برعهده دارد. نحوه انجام این عمل به‌صورت زیر است:

اگر استثنا از داخل یک بلوک try تولید شده باشد، CLR شروع به جست‌وجو در بلوک‌های catch می‌کند. پیمایش بلوک‌ها به همان ترتیبی که در مرحله کدنویسی مشخص شده، مورد بررسی قرار می‌گیرد، این‌کار تا زمانی‌که بلوکی بتواند خطا را اداره کند، ادامه می‌یابد و در صورتی‌که هیچ بلوکی توانایی handle کردن خطا را نداشته باشد، CLR پشته را برای یافتن بلوکی که بتواند خطا را اداره کند، جست‌وجو می‌کند. این فرآیند ادامه می‌یابد تا به ابتدای پشته برسد و اگر هیچ بلوک catch که توانایی اداره کردن خطا را داشته باشد، وجود نداشت، در آن صورت سازنده پیش‌فرض استثنا را اجرا کرده و یک Unhandled exception یا خطای اداره نشده را تولید کرده و پردازش را در اصطلاح shut down یا terminate می‌کند که نتیجه آن خاتمه ناگهانی برنامه است. این امر به این موضوع اشاره دارد که برنامه دارای یک باگ است که باید برطرف شود. در صورتی‌که بلوک catch هماهنگ پیدا شود، دستورات داخل بلوک catch اجرا می‌شود. در این میان، اگر بلوک finally نیز وجود داشته باشد، دستورات آن بعد از اجرای بلوک catch اجرا می‌شود.

 

کلاس Exception

کلاس Exception کلاسی پایه برای همه Exceptionها است. این کلاس از Object مشتق شده است و کلاس‌های استثنا از این کلاس مشتق شده و شی‌ءهای استثنا را تولید (پرتاب) می‌کنند. ترکیب نحوی این کلاس به‌صورت زیر است:

[serializableAttribute]

[ComVisibleAttribute(true)]

[ClassInterfaceAttribute(ClassInterfaceType.None)]

public class Exception : ISerializable, _Exception

کلاس بالا با دارا بودن خاصیت‌ها و متدهای مختلف، این امکان را فراهم می‌آورد که در صورت مواجه شدن با یک استثنا بتوانید اطلاعات مفیدی از شیء استثنا به‌دست آورده، علت بروز خطا را متوجه شده و آن‌را برطرف کنید. خاصیت‌هایی که این کلاس شامل می‌شود، عبارتند از:Message,HelpLink,InnerException,StakTrace,Data,InnerException,Source,TargetSite (توضیحات مربوط به این خاصیت‌ها بعدا ارائه خواهد شد). هر کدام از این خاصیت‌ها بیانگر اطلاعاتی درمورد خطای رخ‌داده است. به‌طور مثال زمانی‌که یک استثنا سبب بروز استثنای دیگری شود، می‌توان آن‌را با InnerException رصد کرد .

 

کلاس SystemException

SystemExeption یکی از کلاس‌های مشتق شده از کلاس Exception است. استثنا‌های تولید‌شده توسط CLR از این کلاس مشتق می‌شوند.

 

کلاس ApplicationException

کلاس ApplicationException یکی دیگر از کلاس‌های مشتق شده از Exception است. برنامه‌های کاربر و نه CLR استثنا‌های سفارشی را که از این کلاس مشتق شده است، پرتاب می‌کنند. زمانی‌که صحبت از ساخت یک کلاس سفارشی می‌شود، انتخاب میان ApplicationExeption و Exception سخت است و درباره این‌که کدام‌یک از این کلاس‌ها برای ساخت یک کلاس سفارشی باید مورد استفاده قرار گیرد، اختلاف نظر وجود دارد.

در زمان ارائه نسخه‌های اولیه .Net عنوان می‌شد که باید از ApplicationException استفاده شود اما بعداً مایکروسافت در مستنداتی که در سایت MSDN ارائه کرده است، به این نکته اشاره می‌کند که از کلاس Exception استفاده کنید. متن زیر برگرفته از سایت MSDN در این خصوص است۱:

For most applications, derive custom exceptions from the Exception class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value.

 

سلسله مراتب استثنا‌ها

یکی از مزیت‌های استفاده از چارچوب دات نت‌، کدنویسی در یک محیط مدیریت شده با استفاده از قابلیت‌های CLR به‌عنوان یکی از مؤلفه‌های اصلی .Net است. CLR یا همان Common Language Runtime به‌عنوان هسته اصلی چارچوب .Net مسئولیت اجرای وظایف مختلفی را بر عهده دارد که عملیاتی مانند مدیریت حافظه و مدیریت استثناها نمونه‌ای از این وظایف است. کلاس‌های Exception درون چارچوب .net یک ساختار سلسله مراتبی دارند به‌طوری‌که کلاس Exception به‌عنوان کلاس پایه برای همه کلاس‌های استثنا تعریف شده است. به‌دلیل ارث‌بری کلاس‌های استثنا از این کلاس، امکان استفاده از متدها و خاصیت‌های درون این کلاس در کلاس‌های دیگر نیز وجود دارد.

به‌طور کل، استثناها به دو صورت اتفاق می‌افتند؛ در حالت اول توسط .Net runtime و در حالت دوم توسط برنامه‌نویس ایجاد می‌شوند. دیاگرام ترسیمی برای کلاس‌های Exception چیزی شبیه به شکل۱ خواهد بود. البته کلاس‌های Exception انواع زیادی دارند که هر کدام نیز دارای مشتقات مختلفی هستند. شکل۱ یک طرح کلی از این ساختار را نشان می‌دهد (به‌طور مثال، کلاس IOException یکی از کلاس‌هایی است که از SystemException مشتق می‌شود، در حالی‌که IOException خود دارای مشتقات دیگری است).

dotnet-140-exception01.jpg

شکل1: سیستم سلسله مراتبی توارث استثناها در NET.

همان‌گونه که درشکل۱ می‌بینید، کلاس Exception به‌عنوان کلاس والد (parent) برای همه کلاس‌های استثنا عمل می‌کند و کلاس‌های System.Exception و ApplicationException نیز از مشتقات اصلی آن به‌شمار می‌روند که هر کدام دارای زیر‌کلاس‌های دیگری هستند. شیء‌های Exception که توسط CLR تولید می‌شوند، در بیشتر اوقات از کلاس System.Exception مشتق می‌شوند. (اما این قاعده همواره به صورت کامل صدق نمی‌کند.) مدیریت خطاها شناسایی یک Exception

در چارچوب .Net همه زبان‌های برنامه‌نویسی تحت .Net از مزیت‌های exception handling بهره‌می‌برند. برای این منظور، از یک مکانیزم ساخت یافته که به‌صورت بلوک‌هایی برای *****‌کردن کدها مشخص شده است، استفاده می‌شود. این بلوک‌ها که با‌نام‌های try,catch,finally وجود دارند، در زمان بروز استثنا‌، آن‌ را دریافت کرده و واکنش نشان می‌دهند و به این‌گونه از خاتمه یافتن ناگهانی برنامه جلوگیری به‌عمل می‌آورند. شکل۲، نمونه‌ای از مکانیزم عملکرد این بلوک را در حالت کلی نشان می‌دهد:

dotnet-140-exception.jpg

شکل2: دیاگرام نحوه عملکرد بلوک‌های try , catch, finally

قطعه کد زیر نحوه پیاده‌سازی یک بلوک try/catch را نشان می‌دهد:

private void MyMethod()

{

try

{

//کدی که ممکن است تولید یک استثنا کند

}

catch(FormatException ex)

{

// واکنش به استثنا اجرا شده

}

catch(IOException ex)

{

// واکنش به استثنا اجرا شده

}

catch

{

throw;

}

finally

{

//کد درون این قسمت همیشه اجرا می‌شود، خواه استثنایی تولید شده باشد یا خیر

//از این قسمت می‌توان برای آزاد‌سازی منابع استفاده شده توسط برنامه استفاده کرد

}

}

Try

کدی که روند اجرای آن ممکن است به درستی انجام نشود و احتمال بروز خطا در زمان اجرای آن وجود داشته و به بررسی نیاز دارد، در بلوک try قرار می‌گیرد. به‌عنوان یک پیشنهاد، اگر از دستوراتی استفاده می‌کنید که این دستورات ممکن است سبب بروز خطاهای مختلفی باشند، بهتر است که هر‌کدام را در یک بلوک جداگانه try/catch قرار دهید.

 

catch

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

 

finally

بلوک finally به‌صورت اختیاری می‌تواند در بلوک try مورد استفاده قرار گیرد. کدهای قرار گرفته در این بلوک finally همواره اجرا خواهند شد، بنابراین، مکان مناسبی برای عملیات آزاد‌سازی منابع استفاده شده است. آزادسازی اشیاء پایگاه داده یا بستن Handle یک فایل، نمونه‌ای از مواردی است که می‌توانند درون این بلوک قرار گیرند. مثال زیر نمونه‌ای از نحوه استفاده از این بلوک‌ها را نشان می‌دهد:

 

FileStream fileStream = null;

try

{

fileStream = File.OpenRead(«Hdl.jpg»);

}

catch(System.IO.FileNotFoundException ex)

{

Console.WriteLine(ex.ToString());

}

finally

{

//کد درون این قسمت همراه اجرا می‌شود

if (fileStream != null)

{

fileStream.Close();

}

}

 

یکی دیگر از روش‌های اداره‌کردن استثنا‌ها، استفاده از بلوک‌های تودرتوی try/catch است. هرچند این روش کم‌تر مورد استفاده قرار می‌گیرد، اما در برخی شرایط نیاز به استفاده از این روش خواهید داشت. بنابراین بهتر است با نحوه پیاده‌سازی این‌گونه بلوک‌ها آشنا باشید. قطعه کد زیر یک مثال از مکانیزم پیاده‌سازی این

روش است:

 

int[] myarray = new int[3];

for (int i = 0; i

{

try

{

Console.WriteLine(«Enter Numbers : «);

try

{

myarray = int.Parse(Console.ReadLine());

}

catch (IndexOutOfRangeException e)

{

Console.WriteLine(e.Message);

}

catch (FormatException e)

{

Console.WriteLine(e.Message);

}

}

catch (OverflowException e)

{

Console.WriteLine(e.Message);

}

}

 

 

در صورتی‌که مقدار وارد شده در مثال بالا از نوع کاراکتر باشد، بلوک FormatException به آن واکنش نشان خواهد داد اما در‌نهایت بلوک IndexOutofRangeException که نشان‌دهنده تجاوز از حد ارائه است، رخ خواهد داد. نمونه خروجی برنامه را در شکل ۳ مشاهده می‌کنید.

dotnet-140-01.jpg

شکل 3

Throw an exception

همان‌گونه که عنوان شد، استثنا‌ها مکانیزمی هستند که .Net برای گزارش خطاها از آن‌ها استفاده می‌کند. با استفاده از اشیاء استثنا نوع خطای رخ داده به همراه علت آن مشخص می‌شود و می‌توانید تعیین کنید که چه عملی برای اداره کردن آن باید انجام گیرد؛ که به این امر Exception Handling می‌گویند. در ابتدای مقاله، عنوان شد که یک استثنا ممکن است توسط یک برنامه‌نویس تولید شود، اما پرسشی که به‌وجود می‌آید؛ این است که چقدر زمان لازم است تا به‌طور دستی یک استثنا را پرتاب کنید؟ در پاسخ باید گفت زمانی‌که متدی وظیفه خود را به‌درستی انجام نمی‌دهد، لازم است که یک استثنا پرتاب شود. برای پرتاب یک استثنا در زمان اجرا از کلمه کلیدی throw به همراه نام استثنای مورد نظر استفاده می‌شود. هنگام پرتاب یک استثنای لازم است که توضیحاتی را در‌خصوص استثنای ایجاد شده ارائه کنید تا علت وقوع آن واضح شود. اما پرتاب کردن استثنا‌ها باید با احتیاط صورت گیرد. مثلاً برای مواردی مانند ورودی نامعتبری که کاربر ممکن است آن‌ها را در فیلدهای ورودی وارد کند، نباید یک استثنا پرتاب شود و این موضوع با نمایش یک پیغام ساده می‌تواند پوشش داده شود.

مثال زیر، نحوه انجام این عمل را نشان می‌دهد. در قطعه کد زیر اگر ورودی متد Show برابر با مقدار خالی یا null باشد، استثنای ArgumentNullException اجرا خواهد شد (یا در اصطلاح پرتاب خواهد شد). به‌دلیل این‌که از بلوک

try/catch استفاده نمی‌کنیم، برنامه با نمایش پیغام خطا پایان می‌یابد. در زمان پرتاب یک استثنا به‌طور دستی بهتر است از یک عبارت که توصیف‌کننده خطا است، استفاده کنید. (همان‌گونه که گفته شد، برای مواردی مانند مثال زیر نباید از یک استثنا استفاده کرد) از ترکیب نحوی throw برای پرتاب یک استثنا استفاده می‌شود اما از دستور throw برای پرتاب هر استثنایی نباید استفاده کرد. بعضی از استثنا‌ها باید به‌صورت اتوماتیک اجرا شوند که نمونه‌هایی از آن‌ها را در قسمت‌های بعدی مقاله خواهید دید.

private void Show(string str)

{

if (string.IsNullOrEmpty(str))

{

throw new ArgumentNullException(«Show’s Parameter:», «is empty.»);

}

}

فراخوانی متد بالا با مقدار خالی یا null خروجی زیر را نشان داده و برنامه را خاتمه می‌دهد.

dotnet-140-02.jpg

حال کد بالا را با استفاده از بلوک try امتحان می‌کنیم:

try

{

Show(null);

}

catch (ArgumentNullException ex)

{

Console.WriteLine(ex.Message);

}

در قطعه کد بالا بلوک catch استثنای تولید شده را دریافت کرده و برنامه به‌طور ناگهانی خاتمه نمی‌یابد. پرتاب دوباره استثنا

یکی از مواردی که ممکن است با آن روبه‌رو شوید یا در کدهای نوشته شده توسط برنامه‌نویسان دیگر، آن‌را ببینید، پرتاب دوباره یک استثنا است، اما به‌دلیل ناآگاهی برخی از برنامه‌نویسان نتیجه اجرای کد می‌تواند متفاوت با چیزی باشد که تصور می‌شود. زمانی که یک شیء استثنا پرتاب می‌شود، نقطه‌ای که استثنا از آن تولید شده است، توسط CLR در پشته ثبت می‌شود و با استفاده از خاصیت stackTrace این اطلاعات با‌ارزش در‌خصوص علت وقوع خطا کمک‌کننده خواهد بود. اما این اطلاعات دائمی نیستند؛ به این معنی که خاصیت stackTrace هر بار که یک شیء استثنا پرتاب می‌شود، ری‌ست می‌شود. به مثال زیر توجه کنید:

 

try

{

......

{

catch (Exception ex)

{

.....

throw ex;

}

 

دستورات throw ex که در بالا استفاده شده است، باعث می‌شود اطلاعات StackTrace در‌خصوص استثنای رخ داده، از دست برود و در این حالت باید از ابزارهای debugger برای کشف خطا استفاده کرد، که کار را سخت می‌کند. برای حل این مشکل، باید همان استثنای تولید‌شده را دوباره پرتاب کنید. ترکیبی که سی‌شارپ ارائه می‌کند، ساده است. در این روش کلمه throw به‌تنهایی مورد استفاده قرار می‌گیرد؛ بدون هیچ‌گونه عبارتی. در نتیجه کد بالا به‌صورت زیر پیاده‌سازی می‌شود:

try

{

......

{

catch (Exception ex)

{

....

//کدهایی که در ارتباط با استثنا هستند

throw;

}

 

در این مقاله با استثنا‌ها آشنا شدید و دریافتید که این اشیا چه هستند، سلسله مراتب آن‌ها چگونه است، چطور با استفاده از بلوک‌های try/catch/finally می‌توانید آن‌ها را مدیریت کنید و در‌نهایت چگونه می‌توانید به‌طور دستی یک استثنا را پرتاب کنید. درباره انواع دیگر خطاها و نحوه مدیریت آن‌ها، شماره آینده ادامه

خواهیم داد.

 

پی‌نوشت

۱-

برای مشاهده این محتوا لطفاً ثبت نام کنید یا وارد شوید.

  • Like 2
لینک به دیدگاه
×
×
  • اضافه کردن...