مدیریت خطا در جاوا

خطاها و ارورها همیشه در دنیای برنامه نویسی وجود دارند! این خطاها ممکن هست به دلیل نادرست بودن ورودی کاربر، عدم پاسخگویی سیستم و یا حتی اشتباهات برنامه نویسی به وجود بیایند. به طور کل میتوان گفت که ارورها در هنگام اجرای برنامه ها نمایان میشوند و اگر نتوانیم آنها به خوبی مدیریت کنیم، اجرای برنامه مختل شده و به طور عامیانه تر برنامه crash خواهد کرد.

 

زبان برنامه نویسی جاوا یک مکانیزم مدیریت خطا (Error handling) قوی که اکثرا به آن Exception Handling یا مدیریت استثنا هم میگویند را داراست.

به خطاهای زمان اجرا (Runtime Error) در جاوا، استثنا یا Exception گفته میشود.

 

در ادامه این مقاله به بررسی چگونگی استفاده از امکانات مدیریت خطا در زبان برنامه نویسی JAVA خواهیم پرداخت.

 

یکی از اصلی ترین روش ها که روشی کلاسیک نیز هست، استفاده از بلوک های try-catch یا به صورت کامل تر بلوک های try-catch-finally برای مدیریت خطا می باشد.

همانطور که اسم این بلوک ها پیداست، از سه بخش کلی تشکیل شده اند:

  • بلوک try: بخشی از کد که ممکن است باعث ایجاد خطا شود و در صورتی که خطایی رخ ندهد، تمام کدهای موجود در این بلوک اجرا خواهد شد.
  • بلوک catch: این بلوک (یا بلوک ها) برای مدیریت خطاهایی است که ممکن است در بخش قبلی رخ دهد. معمولاً هر بلوک را برای یک نوع خطا در نظر میگیرند.
  • بلوک finally: کدهای موجود در این بلوک پس از اجرای موفق یا ناموفق بلوک try اجرا میشود. (در هر دو حالت بروز خطا یا عدم بروز خطا)

 

ساختار کلی استفاده از این بلوک ها به صورت شبه کد زیر است.

try{
    // Protected Code (The Code That May Throw Exception)
}catch(ExceptionType e){
    // Run if ExceptionType Occur
}finally{
    // Run Anyway!
}

 

همانطور که میبینید، در ابتدا بلوک try، پس از آن بلوک (یا بلوک های) catch و در صورت نیاز بلوک finally در انتهای آنها قرار میگیرد.

به یاد داشته باشید که به محتوای درون بلوک try، کد محافظت شده (protected code) نیز گفته میشود.

 

فرض کنید درون یک کلاس متدی با نام readFile() داریم که میخواهیم در آن یک فایل که آدرس آن در متغیری با نام filename درون شئ وجود دارد خوانده و چاپ کنیم.

میتوانید جهت آشنایی کار با فایل در زبان JAVA، مقاله خواندن فایل در زبان جاوا را مطالعه نمایید.

 

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

برای سادگی در مرحله اول، سعی میکنیم تمام خطاهای ممکن را با استفاده از یک بلوک catch مدیریت کنیم.

public void readFile() {
    try {
        FileReader fr = new FileReader(fileName);
        BufferedReader br = new BufferedReader(fr);

        while( (String line = br.readLine() ) != null ) {
            System.out.println(line);
        }
        br.close();

    }catch(Exception e) {
        e.printStackTrace();
    }
}

 

در قطعه کد بالا، در صورتی که مشکلی در باز کردن فایل وجود نداشته باشد، بلوک try به طور کامل و تا انتها اجرا شده و اجرای متد به اتمام میرسد.

اما اگر مشکلی در باز کردن فایل وجود داشته باشد، اجرای ادامه کدها در بلوک try متوقف شده و به بلوک catch می پریم.

دقت داشته باشید که در صورت بروز خطا، بلوک catch مربوطه اجرا شده و ( در صورت وجود بلوک finally) بلوک finally اجرا میشود. سپس از مجموعه بلوک های try-catch-finally خارج شده و ادامه برنامه را اجرا میکند؛ در نتیجه با برخورد به یک خطا، برنامه ما متوقف نخواهد شد.

 

همانطور که می بینید در بلوک catch یک نمونه از کلاس Exception را در اختیار داریم که یک شئ از کلاس مربوط به استثنا (یا همان خطای) پیش آمده است و میتوانیم متدهای مختلفی که در این کلاس وجود دارد را استفاده کرده و صدا بزنیم.

 

در مثالی که زدیم (باز کردن یک فایل) ممکن است به دلایل مختلفی نتوانیم فایل را باز کنیم؛ ممکن است فایل قفل بوده و در حال نوشته شدن توسط برنامه دیگری باشد و یا چنین فایلی در مسیر داده شده وجود نداشته باشد یا ….

اگر بخواهیم برای یکسری از خطاهای خاص، عملیات های متفاوتی انجام دهیم میتوانیم از چندین بلوک catch استفاده نماییم. برای مثال اگر فایلی در مسیر مورد نظر وجود نداشته باشد پیام دلخواهم خود را به کاربر نمایش داده و در صورت بروز خطایی غیر از این، پیام پیشفرض خطای پیش آمده را چاپ کنیم.

خطاهای مربوط به باز کردن یک فایل از نوع IOException هستند و خطای FileNotFoundException نیز مربوط به عدم وجود داشتن فایل در مسیر داده شده می باشد که زیر مجموعه از از همان کلاس IOException است.

public void readFile() {
    try {
        FileReader fr = new FileReader(fileName);
        BufferedReader br = new BufferedReader(fr);
        while( (String line = br.readLine() ) != null ) {
            System.out.println(line);
        }
        br.close();

    }catch(FileNotFoundException e1){
        System.out.println("File Not Found!");

    }catch(IOException e2){
        System.out.println(e2.getMessage());
    }
}

 

به همین راحتی میتوانیم با توجه به نوع خطای پیش آمده، عملیات مرتبط با آن را انجام دهیم؛ مثلاً پیام مرتبط با خطا را نشان داده و یا کد (با توجه به ساختار دلخواه برنامه خود) خطا را به عنوان نتیجه باز گردانیم.

 

استفاده بلوک finally در try-catch

در صورتی که بخواهیم پس از اجرای با خطا یا بدون خطای یک قطعه کد، عملیات خاصی را انجام دهیم (مثلا وضعیت سیستم را log کنیم!) میتوانیم از بلوک finally در ادامه بلوک های try-catch خود استفاده کنیم.

اکنون میخواهیم در انتهای اجرای متد readFile یک متن را چاپ کنیم.

try {
    FileReader fr = new FileReader(fileName);
    BufferedReader br = new BufferedReader(fr);
    while( (String line = br.readLine() ) != null ) {
        System.out.println(line);
    }
    br.close();
}catch(Exception e) {
    e.printStackTrace();

}finally{
    System.out.println("Ended!");
}

 

متدهای مورد استفاده در Exception ها

همه Exception ها به دلیل اینکه زیرمجموعه کلاس Throwable هستند (از آن ارث برده اند) متدهای مشترکی با هم دارند. متدهای مختلفی وجود دارد که چند مورد از پر استفاده ترین آنها، متدهای زیر هستند.

  • متد getMessage : پیام مرتبط با استثنای رخ داده را در فرمت رشته (String) به ما میدهد.
  • متد printStackTrace : نوع خروجی این متد void است و در صورت صدا زدن سلسله مراتب متدهای کلاس های مختلف که منجر به رخ دادن این خطا شده اند را در خروجی چاپ میکند. این تابع از آنجا که مسیر صدا زده شدن توابع مختلف را تا رسیدن به خطای رخ داده نمایش میدهد، کمک بسیاری زیادی به فرآیند رفع خطای کدها (Debugging) میکند.
  • متد getStackTrace : همان سلسله مراتب اجرای توابع مختلف تا رسیدن به استثنای رخ داده را در قالب یک آرایه به ما خروجی میدهد.