PDA

View Full Version : آموزش: مدیریت خطاها و استثنائات (Exception) و نمایش پیام مناسب با توجه به خطای رخ داده در دلفی



M_Maskout
جمعه 12 فروردین 1390, 20:33 عصر
مقدمه
شیئ Application، ویژگی‌ها، متدها و وقفه‌های جالبی داره که به کمک اونا می‌شه خیلی از مدیریت‌ها رو روی کل برنامه انجام داد.
مثلاً برای فارسی کردن کیبورد، هرچند می‌شه از کد زیر استفاده کرد

LoadKeyboardLayout('00000429', KLF_ACTIVATE);

اما همینطور می‌شه از ویژگی BiDiKeyboard بصورت

Application.BiDiKeyboard := '00000429';

استفاده کرد. یا مثلاً متدهای ProcessMessages و HandleMessage که برای جلوگیری از قفل شدن کنسول برنامه و یا حتی کل ویندوز در زمان اجرای حلقه‌ها بسیار مفیدن.
یکی از کارهایی می‌شه با شیء Application انجام داد، مدیریت خطاهای کل برنامه توسط متد OnException از این شیئه. اصولاً اینکار (مدیریت خطاها) برای اجرای هر چه روان‌تر برنامه لازمه و به عقیده من باید انجام بشه. مخصوصاً تو برنامه‌های فارسی مدیریت خطاهای پیش بینی نشده علاوه بر روانتر شدن محیط کاربری، برنامه رو حرفه‌ای‌تر و قابل استفاده‌تر می‌کنه. به شکل زیر توجه کنید:

68144

".tblTemp: Missing TableName property"؛ شاید حتی دوستان کاملاً حرفه‌ای هم نتونن به درستی تشخیص بدن که این پیام از چه شرایطی نشأت گرفته.
این خطا در زمانی پیش اومده که من می‌خواستم یه Table رو باز کنم در حالیکه اتصال اون به بانک اطلاعاتی برقرار نشده.

ADOConnection.Connected := False;
tblTemp.Active := True;

پر واضحه که از متن پیام چنین مطلبی برداشت نمی‌شه. حالا پیام زیر رو در نظر بگیرین

68145

در اینجا علاوه بر اینکه می‌شه از یه MessageBox کاملاً شخصی استفاده کرد، می‌تونید پیام مناسب رو به فارسی نشون بدین، حتی با کمی صرف وقت بیشتر و به منظور داشتن کنترل بیشتر می‌شه نام Tabel، نام یا caption فرمی که خطا در اون اتفاق افتاده و یا خیلی چیزای دیگه رو به کاربر نشون داد.
در نظر بیارین زمانی رو که برنامه رو به یه کاربر تو یه شهر دیگه دادین، بعد یه روز با شما تماس می‌گیره و پیام اول رو می‌خونه؛ قطعاً چندتا مشکل رو باید پشت سر بذارین تا بشه یه کم به اون راهنمایی بدین. (کاربر، توان خوندن انکلیسی نداره، کامپیوتر هم بلد نیست، غرورش هم اجازه نمی‌ده از کس دیگه‌ای همونجا کمک بگیره و ...). ولی پیام دوم رو به راحتی می‌تونه بفهمه، حتی با یه آموزش نیم بند، شاید بتونه مشکلات اینچنینی بعدی رو هم رفع کنه.
دوستانی که با QBasic و همینطور نسخه‌های حرفه‌ای‌تر بعدی اون (مثلاً Quick Basic و یا VB6) آشنایی دارن، می‌دونن که توی این زبان با استفاده از دستور
ON ERROR GOTO line-number
می‌شه به راحتی خطاهای پیش بینی نشده رو مدیریت کرد. اما دلفی هم ابزارهای بسیار قدرتمندی برای این منظور داره. هرچند به نظر من حتی QBasic هم این کار رو بسیار روان‌تر انجام می‌ده ولی با داشتن دانش و تسلط کافی روی دلفی، مدیریت خطاها (در کل برنامه) کار خیلی ساده و جذابی خواهد بود.
---------------------------------------------------------------------
برای مدیریت خطاهای پیش بینی نشده در برنامه، لازمه یه روتین برای وقفه‌ی OnException از شیء Application بنویسین و توی اون روتین، خطاهای پیش اومده در کل برنامه رو مدیریت کنین. در اینجا دست شما برای کنترل انواع خطا در زمان رویدار اون باز هست.
مثلاً در وقفه FormCreate از فرم اصلی برنامه دستور

Application.OnException := MyException;

رو قرار بدین بعد هم روتین MyException رو در ادامه به عنوان یکی از متدهای همین فرم تعریف کنین. یه چیزی مثل این:

TfrmMainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure MyException(Sender: TObject; E: Exception);
end;

procedure TfrmMainForm.FormCreate(Sender: TObject);
begin
Application.OnException := MyException;
end;

procedure TfrmMainForm.MyException(Sender: TObject; E: Exception);
begin
ShowMessage(E.Message);
end;


از پارامتر E برای تشخیص نوع خطا می‌تونید استفاده کنین. وقتی خطایی در برنامه رخ می‌ده، دلفی یه فرزند از کلاس TObject می‌سازه و خطای بوجود اومده رو با اون نمایش می‌ده.
با استفاده از پارامتر E و تبدیل اون به کلاس ساخته شده‌ی توسط دلفی، می‌شه خطای بوجود اومده رو مدیریت کرد.
در اینجا اگر بجای دستور

ShowMessage(E.Message);

از دستور

ShowMessage(E.Message+' ('+E.ClassName+')');

استفاده بشه، در زمان بروز خطا، عبارت داخل پرانتز در MessageBox نمایش داده شده، نام کلاس خطا نمایش داده می‌شه.
خطاها یا استثنائات در دلفی چند دسته هستند. که من در اینجا به تعداد از اونا اشاره می‌کنم و از سایر دوستان خواهش می‌کنم علاوه بر اعلام اشکالات متن حاضر، مطالب جا افتاده و تکمیلی رو در این تاپیک بیان کنند.
ضمناً در ادامه واژه استثنا معادل خطا به کار رفته و این به دلیل این هست که دلفی از کلمه Exception استفاده می‌کنه.
استثنائات ریاضی:
استثنائاتی که در زمان عملیات ریاضی بوجود میان. مثل تقسیم بر صفر این استثنائات عبارتند از:
EDivByZero : (علت وقوع) تقسیم یک عدد صحیح بر صفر.
ERangeError : (علت وقوع) عدد مورد نظر از دامنه‌ی تعریف شده خارج است (out of range).
EIntOverflow : (علت وقوع) سرریزی (overflow) در عملیات بر روی عدد صحیح.
EZeroDivide : (علت وقوع) تقسیم یک عدد حقیقی (اعشاری) بر صفر.

استثنائات ورودی / خروجی:
EInOutError : (علت وقوع) اشکال در عملیات I/O.
در این نوع از استثنا می‌شه بوسیله ویژگی ErrorCode کنترل بیشتری بر روی خطای بوجود آمده انجام داد.
لیست کدهای خطاهای I/O خارج از حوصله این بحثه.

استثنائات پایگاه داده:
EDatabaseError : تمامی خطاهای مربوط به دسترسی به پایگاه داده را پوشش می‌دهد.
------------------------------------------------------------
برای مطالعه بیشتر به تاپیک زیر سری بزنین:
http://barnamenevis.org/showthread.php?169337-کاربرد-Raise-چیست؟&p=761790&viewfull=1#post761790

a_mosavian
جمعه 12 فروردین 1390, 22:40 عصر
این تاپیک هم بحث مختصری در این باره شده:
http://barnamenevis.org/showthread.php?106794-%D9%81%D8%A7%D8%B1%D8%B3%DB%8C-%DA%A9%D8%B1%D8%AF%D9%86-%D9%BE%DB%8C%D8%BA%D8%A7%D9%85-%D9%87%D8%A7%DB%8C-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87

vcldeveloper
شنبه 13 فروردین 1390, 00:50 صبح
".tblTemp: Missing TableName property"؛ شاید حتی دوستان کاملاً حرفه‌ای هم نتونن به درستی تشخیص بدن که این پیام از چه شرایطی نشأت گرفته.
این خطا در زمانی پیش اومده که من می‌خواستم یه Table رو باز کنم در حالیکه اتصال اون به بانک اطلاعاتی برقرار نشده.
البته در مثال مناقشه نیست، ولی پیامی که اونجا داده شده، بارها از پیامی که شما به کاربر نمایش دادید، از نظر فنی با ارزش تر هست. اون پیام داره میگه که دستور SQL ایی که شما برای موتور بانک اطلاعاتی ارسال کردید، فاقد پارامتر الزامی نام جدول هست. با همچین پیامی برنامه نویس اگر از کوئری استفاده کرده باشه، مستقیما میره سراغ SQL نوشته شده، و اگر از کلاس های Table استفاده کرده باشه، نام جدولی که دیتاست بهش متصل هست را چک میکنه.



دوستانی که با QBasic و همینطور نسخه‌های حرفه‌ای‌تر بعدی اون (مثلاً Quick Basic و یا VB6) آشنایی دارن، می‌دونن که توی این زبان با استفاده از دستور
ON ERROR GOTO line-number
می‌شه به راحتی خطاهای پیش بینی نشده رو مدیریت کرد. اما دلفی هم ابزارهای بسیار قدرتمندی برای این منظور داره. هرچند به نظر من حتی QBasic هم این کار رو بسیار روان‌تر انجام می‌ده ولی با داشتن دانش و تسلط کافی روی دلفی، مدیریت خطاها (در کل برنامه) کار خیلی ساده و جذابی خواهد بود.
چیزی که شما در دلفی دارید، فراتر از یک مکانیزم ساده لاگ کردن خطا هست؛ و بهش Structural Exception Handling (یا به طور اختصار SEH) گفته میشه که خودش یک مبحث پیچیده و پیشرفته در برنامه نویسی هست. ساختارهای try-finally و try-except در دلفی یک مکانیزم سلسله مراتبی برای تشخیص Exception و ارسال آن به ساختارهای بالاتر فراهم می کنند. در هر مرحله بلوک های کنترلی امکان هندل کردن اون Exception رو دارند، و اگر Exception ایی هندل نشه، نهایتا به شی Application میرسه. اما اگر هندل بشه، چیزی به Application نخواهد رسید، مگر اینکه کدی که اون رو هندل کرده، دوباره اون رو raise کنه.

رویدادی مثل Application.OnException برای Log کردن ابتدایی Exception های هندل نشده، میتونه مناسب باشه. البته برای Log های حرفه ایی و درست و حسابی که بشه برای دیباگ ازشون استفاده کرد، نیاز به مکانیزم های پیچیده تری هست، که در ابزارهایی مثل MadException یا EurekaLog پیاده سازی شدند.

a_mosavian
شنبه 13 فروردین 1390, 01:23 صبح
مدیریت استثنا در دلفی بسیار خوب پیاده سازی شده. تنها ایرادش نبود بلوک try..except..finally هست که در زبانهای دات نت داریم. تنها فایده ای که فارسی سازی پیام های استثنا داره اینه که کاربر وحشت نمی کنه. چون به اشتباه گمان می کنه که شما پیاده سازی کدهای مربوط به ایجاد پیغام رو انجام دادید و این یک چیز مدیریت شده از جانب شماست و از این جهت کمی حرفه ای تر هست :لبخند:
روش فارسی سازی پیغام های استثنای ایجاد شده توسط خود دلفی در من توی اون لینک قبلی گذاشتم. از اون بهره بگیرید و دست به رویداد OnException و قسمت except بلوک try..except..end نیازید. کاربرد این رویداد رو آقای کشاورز در پست پیشین به درستی بیان کردند. وانگهی دستکاری و ناقص کردن اطلاعات ارائه شده توسط پیغام خطا بدست شما می تونه از حل باگ هایی که خود کاربران بهش بر می خورند را ناممکن کنه.

M_Maskout
شنبه 13 فروردین 1390, 21:09 عصر
چیزی که شما در دلفی دارید، فراتر از یک مکانیزم ساده لاگ کردن خطا هست؛ و بهش Structural Exception Handling (یا به طور اختصار SEH) گفته میشه که خودش یک مبحث پیچیده و پیشرفته در برنامه نویسی هست. ساختارهای try-finally و try-except در دلفی یک مکانیزم سلسله مراتبی برای تشخیص Exception و ارسال آن به ساختارهای بالاتر فراهم می کنند. در هر مرحله بلوک های کنترلی امکان هندل کردن اون Exception رو دارند، و اگر Exception ایی هندل نشه، نهایتا به شی Application میرسه.
در تأیید نوشته‌های آقای کشاورز به یه نکته از کتاب پر ارزش Mastering Delphi 7 نوشته آقای مارکو کانتو اشاره می‌کنم. ایشون تو فصل دوم کتاب، در پایان بخش Program Flow and the" finally Block" (روند برنامه و بلاک finally) این طور نوشتن:


Handling the exception is generally much less important than using finally blocks, because Delphi can survive most exceptions. Too many exception-handling blocks in your code probably indicate errors in the program flow and possibly a misunderstanding of the role of exceptions in the language.

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

Exception: Base class
EAbort: Abort without dialog
EAbstractError: Abstract method error
AssertionFailed: Assert call failed
EBitsError: Boolean array error
ECommonCalendarError: Calendar calc error
EDateTimeError: DateTime calc error
EMonthCalError: Month calc error
EConversionError: Raised by Convert
EConvertError: Object convert error
EDatabaseError: Database error
EExternal: Hardware/Windows error
EAccessViolation: Access violation
EControlC: User abort occured
EExternalException: Other Internal error
EIntError: Integer calc error
EDivByZero: Integer Divide by zero
EIntOverflow: Integer overflow
ERangeError: Out of value range
EMathError: Floating point error
EInvalidArgument: Bad argument value
EInvalidOp: Inappropriate operation
EOverflow: Value too large
EUnderflow: Value too small
EZeroDivide: Floating Divide by zero
EStackOverflow: Severe Delphi problem
EHeapException: Dynamic memory problem
EInvalidPointer: Bad memory pointer
EOutOfMemory: Cannot allocate memory
EInOutError: IO error
EInvalidCast: Object casting error
EInvalidOperation: Bad component op
EMenuError: Menu item error
EOSError: Operating system error
EParserError: Parsing error
EPrinter: Printer error
EPropertyError: Class property error#
EPropReadOnly: Invalid property access
EPropWriteOnly: Invalid property access
EThread: Thread error
EVariantError: Variant problem


این هم لینک دانلود کامپوننت‌های jcl و JVCL (غیر مستقیم و با حجم تقریباً Mb‏ 19.5)، ابزاری برای مدیریت استثائات در برنامه‌ی اجرایی
http://jaist.dl.sourceforge.net/project/jvcl/JVCL%203/JVCL%203.40/JVCL340CompleteJCL221-Build3845.zip
برای دریافت اطلاعات در مورد این کامپوننت به پست زیر نگاه کنید:
http://barnamenevis.org/showthread.php?127092-Advanced-Exception-Handler-.-یافتن-دقیق-مکان-خطا.&p=615322&viewfull=1#post615322

M_Maskout
پنج شنبه 18 فروردین 1390, 00:30 صبح
تو چندتا پست از تاپیک زیر، مطالب خیلی جالبی در مورد مدیریت خطا تو دلفی نوشته شده:

http://barnamenevis.org/showthread.php?280265-چك-كردن-ديتابيس-SQL-در-هنگام-اجراي-برنامه&p=1241420&viewfull=1#post1241420

با تشکر فراوان از دوستان عزیز a_mosavian (http://barnamenevis.org/member.php?88282-a_mosavian) و علی کشاورز (http://barnamenevis.org/member.php?4729-علی-کشاورز)