Merancang kelas pengecualian

9

Saya sedang mengode perpustakaan kecil dan saya mengalami masalah dengan merancang penanganan pengecualian. Saya harus mengatakan bahwa saya (masih) bingung dengan fitur bahasa C ++ ini dan saya mencoba membaca sebanyak mungkin tentang masalah ini untuk memahami apa yang harus saya lakukan untuk bekerja dengan benar dengan kelas pengecualian.

Saya memutuskan untuk menggunakan system_errorjenis pendekatan yang mengambil inspirasi dari implementasi future_errorkelas STL .

Saya memiliki enumerasi yang berisi kode kesalahan:

enum class my_errc : int
{
    error_x = 100,
    error_z = 101,
    error_y = 102
};

dan satu kelas pengecualian (didukung oleh error_categoryjenis struktur dan segala sesuatu yang dibutuhkan oleh system_errormodel):

// error category implementation
class my_error_category_impl : public std::error_category
{
    const char* name () const noexcept override
    {
        return "my_lib";
    }

    std::string  message (int ec) const override
    {
        std::string msg;
        switch (my_errc(ec))
        {
        case my_errc::error_x:
            msg = "Failed 1.";
            break;
        case my_errc::error_z:
            msg = "Failed 2.";
            break;
        case my_errc::error_y:
            msg = "Failed 3.";
            break;
        default:
            msg = "unknown.";
        }

        return msg;
    }

    std::error_condition default_error_condition (int ec) const noexcept override
    {
        return std::error_condition(ec, *this);
    }
};

// unique instance of the error category
struct my_category
{
    static const std::error_category& instance () noexcept
    {
        static my_error_category_impl category;
        return category;
    }
};

// overload for error code creation
inline std::error_code make_error_code (my_errc ec) noexcept
{
    return std::error_code(static_cast<int>(ec), my_category::instance());
}

// overload for error condition creation
inline std::error_condition make_error_condition (my_errc ec) noexcept
{
    return std::error_condition(static_cast<int>(ec), my_category::instance());
}

/**
 * Exception type thrown by the lib.
 */
class my_error : public virtual std::runtime_error
{
public:
    explicit my_error (my_errc ec) noexcept :
        std::runtime_error("my_namespace ")
        , internal_code(make_error_code(ec))
    { }

    const char* what () const noexcept override
    {
        return internal_code.message().c_str();
    }

    std::error_code code () const noexcept
    {
        return internal_code;
    }

private:
    std::error_code internal_code;
};

// specialization for error code enumerations
// must be done in the std namespace

    namespace std
    {
    template <>
    struct is_error_code_enum<my_errc> : public true_type { };
    }

Saya hanya memiliki sejumlah kecil situasi di mana saya melempar pengecualian yang diilustrasikan oleh enumerasi kode kesalahan.

Di atas tidak cocok dengan salah satu pengulas saya. Dia berpendapat bahwa saya seharusnya membuat hierarki kelas pengecualian dengan basis kelas yang berasal dari std::runtime_errorkarena memiliki kode kesalahan tertanam dalam kondisi mencampur hal-hal - pengecualian dan kode kesalahan - dan itu akan lebih membosankan untuk berurusan dengan suatu titik penanganan; hierarki pengecualian juga akan memudahkan kustomisasi pesan kesalahan.

Salah satu argumen saya adalah bahwa saya ingin tetap sederhana, bahwa perpustakaan saya tidak perlu membuang beberapa jenis pengecualian dan kustomisasi juga mudah dalam hal ini seperti yang ditangani secara otomatis - yang error_codetelah menjadi error_categoryterkait dengan itu yang menerjemahkan kode ke pesan kesalahan yang tepat.

Saya harus mengatakan bahwa saya tidak mempertahankan pilihan saya dengan baik, bukti fakta bahwa saya masih memiliki beberapa kesalahpahaman tentang pengecualian C ++.

Saya ingin tahu apakah desain saya masuk akal. Apa kelebihan metode lain dari yang saya pilih karena harus saya akui juga gagal melihatnya? Apa yang bisa saya lakukan untuk meningkatkan?

celavek
sumber
2
Saya cenderung setuju secara prinsip dengan reviewer Anda (mencampur kode kesalahan dan pengecualian tidak terlalu berguna). Tetapi kecuali Anda memiliki perpustakaan besar yang memiliki hierarki besar juga tidak berguna. Pengecualian dasar yang berisi string pesan, hanya memiliki pengecualian terpisah jika penangkap pengecualian berpotensi menggunakan keunikan pengecualian untuk memperbaiki masalah.
Martin York

Jawaban:

9

Saya pikir kolega Anda benar: Anda mendesain kasus pengecualian Anda berdasarkan seberapa sederhana penerapannya dalam hierarki, bukan berdasarkan kebutuhan penanganan-pengecualian dari kode klien.

Dengan satu jenis pengecualian dan penghitungan untuk kondisi kesalahan (solusi Anda), jika kode klien perlu menangani kasus kesalahan tunggal (misalnya, my_errc::error_x) mereka harus menulis kode seperti ini:

try {
    your_library.exception_thowing_function();
} catch(const my_error& err) {
    switch(err.code()) { // this could also be an if
    case my_errc::error_x:
        // handle error here
        break;
    default:
        throw; // we are not interested in other errors
    }
}

Dengan beberapa tipe pengecualian (memiliki basis umum untuk seluruh hierarki), Anda dapat menulis:

try {
    your_library.exception_thowing_function();
} catch(const my_error_x& err) {
    // handle error here
}

di mana kelas pengecualian terlihat seperti ini:

// base class for all exceptions in your library
class my_error: public std::runtime_error { ... };

// error x: corresponding to my_errc::error_x condition in your code
class my_error_x: public my_error { ... };

Saat menulis perpustakaan, fokusnya harus pada kemudahan penggunaannya, bukan kemudahan implementasi internal.

Anda hanya harus mengkompromikan kemudahan penggunaan (bagaimana kode klien akan terlihat) ketika upaya melakukannya dengan benar di perpustakaan, adalah hal yang terlarang.

utnapistim
sumber
0

Saya setuju dengan pengulas Anda dan dengan @utnapistim. Anda dapat menggunakan system_errorpendekatan ketika Anda menerapkan hal-hal lintas platform ketika beberapa kesalahan memerlukan penanganan khusus. Tetapi bahkan dalam kasus ini, itu bukan solusi yang baik, tetapi solusi yang kurang jahat.

Satu hal lagi. Saat membuat hierarki pengecualian, jangan membuatnya terlalu dalam. Buat hanya kelas pengecualian itu, yang bisa diproses oleh klien. Dalam kebanyakan kasus, saya hanya menggunakan std::runtime_errordan std::logic_error. Saya melempar std::runtime_errorketika terjadi kesalahan dan dan saya tidak dapat melakukan apa-apa (pengguna mengeluarkan perangkat dari komputer, ia lupa aplikasi itu masih berjalan), dan std::logic_errorketika logika program rusak (Pengguna mencoba untuk menghapus catatan dari database yang tidak ada, tetapi sebelum menghapus operasi ia dapat memeriksanya, jadi dia mendapatkan kesalahan logika).

Dan sebagai pengembang perpustakaan, pikirkan kebutuhan pengguna Anda. Cobalah untuk menggunakannya sendiri dan pikirkan, apakah itu nyaman untuk Anda. Daripada Anda dapat menjelaskan posisi Anda kepada pengulas Anda dengan contoh-contoh kode.


sumber