نسخه قابل چاپ

بخش ۱۰ - رسیدگی به خطاها

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

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

محتویات فایل ورودی می‌تواند چیزی شبیه به این باشد:

3
Gholam 12/02/1360
Ghamar 12/02/1360
Gholi 14/12/1359

فهرست مثال‌ها

حل مسئله بدون رسیدگی به خطاها

این نسخه از برنامه بدون این که به خطاها رسیدگی کند عمل می‌کند.

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
using namespace std;

class Date {
public:
    Date(int d, int m, int y) : day(d), month(m), year(y) {}
    void print() { cout << day << '/' << month << '/' << year; } 
private:
    int day, month, year;
};

class Student {
public:
    Student(string n, Date bd) : name(n), bdate(bd) {}
    void print() { cout << name << '\t'; bdate.print(); }
private:
    string name;
    Date bdate;
};

Date read_date(ifstream& input) {
    int d, m, y;
    char ch;
    input >> d >> ch >> m >> ch >> y;
    return Date(d, m, y);
}

Student read_student(ifstream& input) {
    string name;
    input >> name;
    Date bdate = read_date(input);
    return Student(name, bdate);
}

void read_student_info(char* filename, vector<Student>& v) {
    ifstream input(filename);
    int count;
    input >> count;
    for (int i = 0; i < count; i++) {
        Student s = read_student(input);
        v.push_back(s);
    }
    input.close();
}

void do_some_processing(vector<Student>& v) {
    for (int i = 0; i < v.size(); i++) {
        v[i].print();
        cout << endl;
    }
}

int main(int argc, char* argv[]) {
    vector<Student> students;
    read_student_info(argv[1], students);
    do_some_processing(students);
}
					
رسیدگی به خطا با استفاده از متغیر سراسری

این نسخه از برنامه با استفاده از یک متغیر سراسری error برای انتشار بروز خطا استفاده می‌کند. دقت کنید که این نحوه‌ی انتشار خطا اولاً کد را به یک متغیر سراسری وابسته می‌کند. ثانیاً بعد از تمام فراخوانی‌ها لازم است مقدار متغیر مذکور کنترل شود و این کد را بسیار ناخوانا می‌کند.

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
using namespace std;

int error;

class Date {
public:
    Date(int d, int m, int y);
    void print();
private:
    int day;
    int month;
    int year;
};

bool is_leap_year(int y) {
    int r = y % 33;
    return r==1 || r==5 || r==9 || r==13 || r==17 || r==22 || r==26 || r==30;
}

int days_of_month(int m, int y) {
    if (m < 1 || m > 12) {
        error = 1;
        return 0;
    } else
        error = 0;
    if (m < 7)
        return 31;
    else if (m < 12)
        return 30;
    else
        return is_leap_year(y) ? 30 : 29;
}

Date::Date(int d, int m, int y) {
    if (y < 0 || m < 1 || m > 12 || d < 1 || d > days_of_month(m, y)) {
        error = 1;
        return;
    }
    day = d;
    month = m;
    year = y;
    error = 0;
}

void Date::print() {
    cout << year << '/' << month << '/' << day;
}

Date read_date(ifstream& input) {
    int d, m, y;
    char ch;
    input >> d;
        input >> ch;
    if (ch != '/') {
        error = 1;
        return Date(1,1,1);
    }
        input >> m;
        input >> ch;
    if (ch != '/') {
        error = 1;
        return Date(1,1,1);
    }
    input >> y;
    error = 0;
    return Date(d, m, y);
}

class Student {
public:
    Student(string n, Date bd) : name(n), bdate(bd) {}
    void print() { cout << name << '\t'; bdate.print(); }
private:
    string name;
    Date bdate;
};

Student read_student(ifstream& input) {
    string name;
    input >> name;
    Date bdate = read_date(input);
    if (error == 1)
        return Student("", Date(1,1,1));
    else 
        return Student(name, bdate);
}

void read_student_info(char* filename, vector<Student>& v) {
    ifstream input(filename);
    int count;
    input >> count;
    for (int i = 0; i < count; i++) {
        Student s = read_student(input);
        if (error == 1) {
            cerr << "Error in reading line #" << (i+1) << endl;
            continue;
        }
        v.push_back(s);
    }
}

void do_some_processing(vector<Student>& v) {
    for (int i = 0; i < v.size(); i++) {
        v[i].print();
        cout << endl;
    }
}

int main(int argc, char* argv[]) {
    vector<Student> students;
    read_student_info(argv[1], students);
    do_some_processing(students);
}
					
رسیدگی به خطا با استفاده از استثناها (exceptions)

این نسخه از برنامه با از استثناها برای رسیدگی به خطا کمک می‌گیرد

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
using namespace std;

// Declarations of Date and Student class are the same as before

class Bad_Date_Exception {};

class Date {
public:
    Date(int d, int m, int y);
    void print();
private:
    int day;
    int month;
    int year;
};

bool is_leap_year(int y) {
    int r = y % 33;
    return r==1 || r==5 || r==9 || r==13 || r==17 || r==22 || r==26 || r==30;
}

int days_of_month(int m, int y) {
    if (m < 7)
        return 31;
    else if (m < 12)
        return 30;
    else if (m == 12)
        return is_leap_year(y) ? 30 : 29;
}

Date::Date(int d, int m, int y) {
    if (y < 0 || m < 1 || m > 12 || d < 1 || d > days_of_month(m, y)) {
        throw Bad_Date_Exception();
    }
    day = d;
    month = m;
    year = y;
}

void Date::print() {
    cout << year << '/' << month << '/' << day;
}

class Student {
public:
    Student(string n, Date bd) : name(n), bdate(bd) {}
    void print() { cout << name << '\t'; bdate.print(); }
private:
    string name;
    Date bdate;
};

Date read_date(ifstream& input) {
    int d, m, y;
    char ch;
    input >> d;
    input >> ch;
    if (ch != '/') {
        throw Bad_Date_Exception();
    }
    input >> m;
    input >> ch;
    if (ch != '/') {
        throw Bad_Date_Exception();
    }
    input >> y;
    return Date(d, m, y);
}

Student read_student(ifstream& input) {
    string name;
    input >> name;
    Date bdate = read_date(input);
    return Student(name, bdate);
}

void read_student_info(char* filename, vector<Student>& v) {
    ifstream input(filename);
    int count;
    input >> count;
    for (int i = 0; i < count; i++) {
        try {
            Student s = read_student(input);
            v.push_back(s);
        } catch (Bad_Date_Exception ex) {
            cerr << "Error in line #" << (i+1) << endl;
        }
    }
}

void do_some_processing(vector<Student>& v) {
    for (int i = 0; i < v.size(); i++) {
        v[i].print();
        cout << endl;
    }
}

int main(int argc, char* argv[]) {
    vector<Student> students;
    read_student_info(argv[1], students);
    do_some_processing(students);
}
					
تمرین‌های کوتاه
  1. چرا استفاده از استثناءها به عنوان روشی برای گزارش و رفع خطا از سایر روش‌ها مثل استفاده از متغیر سراسری یا مقدار بازگشتی تابع بهتر است؟
  2. در نسخه‌ی آخر برنامه، نحوه‌ی رسیدگی به خطا را طوری تغییر دهید که با برخورد به اولین سطر حاوی خطا، خواندن اطلاعات دانشجویان متوقف شود و اجرای برنامه بعد از آن به طور عادی ادامه یابد.
  3. در نسخه‌ی آخر برنامه، نحوه‌ی رسیدگی به خطا را طوری تغییر دهید که تاریخ‌های خطادار با ۱/۱/۱ جایگزین شوند و تمامی سطرهای فایل پردازش شوند.
  4. خطای دیگری که ممکن است در برنامه رخ دهد این است که طول نام دانشجو از ۲ کم‌تر باشد. برای این خطا یک کلاس استثنا تعریف کنید و به آن رسیدگی کنید. دقت کنید که برای یک بلوک try می‌توان چند بلوک catch داشت:

    try {
      ...
    } catch (Exception_Type_1 e1) {
      ...
    } catch (Exception_Type_2 e2) {
      ...
    }

  5. خروجی این برنامه چیست؟

    void h() { cout << 5; throw Exception(); cout << 6; }
    void g() { cout << 3; h(); cout << 4; }
    int main() {
      try { cout << 1; g(); cout << 2; } catch (Exception ex) { cout << 7; } cout << 8;
    }

  6. اگر در یک بلوک catch بنویسیم throw; (بدون مشخص کردن نوع استثناء)، همان شیء استثناءی که catch شده مجدداً throw می‌شود (به این کار اصطلاحاً re-throw) کردن می‌گویند. در چه شرایطی چنین کاری انجام می‌دهیم؟
  7. به سؤالات زیر پاسخ دهید. در صورتی که جواب آن را نمی‌دانید برنامه‌ی کوچکی بنویسید و توسط آن جواب را بیابید:
    • اگر در یک بلوک try هیچ استثنایی رخ ندهد بعد از اتمام بلوک try کنترل برنامه به کجا منتقل می‌شود؟
    • اگر یک استثناء داخل یک بلوک try در همان تابع ایجاد شود (نه توسط یکی از توابع داخلی)، چه اتفاقی می‌افتد؟
    • اگر یک استثناء خارج یک بلوک try ایجاد شود چه اتفاقی می‌افتد؟
    • اگر تایپ استثناء رخ داده با هیچ‌یک از catch های مربوطه منطبق نباشد چه می‌شود؟
    • اگر یک بلوک catch یک استثناء ایجاد کند چه می‌شود؟ اگر این استثناء از همان تایپ بلوک catch باشد چه؟