Thiết kế các lớp ngoại lệ


9

Tôi đang mã hóa một thư viện nhỏ và tôi gặp một số rắc rối với việc thiết kế xử lý ngoại lệ. Tôi phải nói rằng tôi (vẫn) bị nhầm lẫn bởi tính năng này của ngôn ngữ C ++ và tôi đã cố gắng đọc càng nhiều càng tốt về chủ đề này để hiểu được những gì tôi sẽ phải làm để làm việc đúng với các lớp ngoại lệ.

Tôi quyết định sử dụng một system_errorkiểu tiếp cận lấy cảm hứng từ việc thực hiện STL của future_errorlớp.

Tôi có một bảng liệt kê chứa các mã lỗi:

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

và một lớp ngoại lệ duy nhất (được hỗ trợ bởi một error_categoryloại cấu trúc và mọi thứ khác cần thiết cho system_errormô hình):

// 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 { };
    }

Tôi chỉ có một số ít tình huống trong đó tôi đưa ra các ngoại lệ được minh họa bằng bảng liệt kê mã lỗi.

Ở trên đã không ngồi tốt với một trong những người đánh giá của tôi. Ông cho rằng tôi nên tạo ra một hệ thống phân cấp các lớp ngoại lệ với một lớp cơ sở xuất phát từ std::runtime_errorviệc có mã lỗi được nhúng trong điều kiện trộn lẫn các thứ - ngoại lệ và mã lỗi - và sẽ tẻ nhạt hơn khi xử lý điểm xử lý; hệ thống phân cấp ngoại lệ cũng sẽ cho phép dễ dàng tùy chỉnh thông báo lỗi.

Một trong những lập luận của tôi là tôi muốn giữ cho nó đơn giản, rằng thư viện của tôi không cần phải đưa ra nhiều loại ngoại lệ và việc tùy chỉnh cũng dễ dàng trong trường hợp này vì nó được xử lý tự động - error_codeerror_categoryliên quan đến nó dịch mã thông báo lỗi thích hợp.

Tôi phải nói rằng tôi đã không bảo vệ tốt sự lựa chọn của mình, bằng chứng là tôi vẫn còn một số hiểu lầm liên quan đến ngoại lệ C ++.

Tôi muốn biết nếu thiết kế của tôi có ý nghĩa. Điều gì sẽ là lợi thế của phương pháp khác so với phương pháp tôi đã chọn vì tôi phải thừa nhận rằng tôi cũng không thấy điều đó? Tôi có thể làm gì để cải thiện?


2
Tôi có xu hướng đồng ý về nguyên tắc với người đánh giá của bạn (trộn mã lỗi và ngoại lệ không thực sự hữu ích). Nhưng trừ khi bạn có một thư viện khổng lồ có một hệ thống phân cấp lớn cũng không hữu ích. Một ngoại lệ cơ sở có chứa một chuỗi thông báo, chỉ có các ngoại lệ riêng nếu người bắt ngoại lệ có thể có khả năng sử dụng tính duy nhất của ngoại lệ để khắc phục sự cố.
Martin York

Câu trả lời:


9

Tôi nghĩ rằng đồng nghiệp của bạn đã đúng: bạn đang thiết kế các trường hợp ngoại lệ của mình dựa trên mức độ đơn giản để thực hiện trong cấu trúc phân cấp, không dựa trên nhu cầu xử lý ngoại lệ của mã máy khách.

Với một loại ngoại lệ và liệt kê cho điều kiện lỗi (giải pháp của bạn), nếu mã máy khách cần xử lý các trường hợp lỗi đơn lẻ (ví dụ my_errc::error_x:) , chúng phải viết mã như thế này:

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
    }
}

Với nhiều loại ngoại lệ (có cơ sở chung cho toàn bộ phân cấp), bạn có thể viết:

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

nơi các lớp ngoại lệ trông như thế này:

// 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 { ... };

Khi viết thư viện, trọng tâm phải là sự dễ sử dụng, không (nhất thiết) dễ thực hiện nội bộ.

Bạn chỉ nên thỏa hiệp tính dễ sử dụng (mã máy khách sẽ trông như thế nào) khi nỗ lực thực hiện nó ngay trong thư viện là nghiêm cấm.


0

Tôi đồng ý với những người đánh giá của bạn và với @utnapistim. Bạn có thể sử dụng system_errorphương pháp tiếp cận khi bạn triển khai những thứ đa nền tảng khi một số lỗi yêu cầu xử lý đặc biệt. Nhưng ngay cả trong trường hợp này, nó không phải là một giải pháp tốt, mà là giải pháp ít tệ hơn.

Một điều nữa. Khi tạo hệ thống phân cấp ngoại lệ, đừng làm cho nó rất sâu. Chỉ tạo các lớp ngoại lệ đó, có thể được xử lý bởi các máy khách. Trong hầu hết các trường hợp tôi chỉ sử dụng std::runtime_errorstd::logic_error. Tôi ném std::runtime_errorkhi có sự cố và tôi không thể làm gì (người dùng đẩy thiết bị ra khỏi máy tính, anh ta quên ứng dụng đó vẫn đang chạy) và std::logic_errorkhi logic chương trình bị hỏng (Người dùng cố gắng xóa bản ghi khỏi cơ sở dữ liệu không tồn tại, nhưng trước khi xóa thao tác, anh ta có thể kiểm tra nó, vì vậy anh ta nhận được lỗi logic).

Và là nhà phát triển thư viện, hãy nghĩ về nhu cầu người dùng của bạn. Cố gắng sử dụng nó cho mình và suy nghĩ, cho dù nó thoải mái cho bạn. Hơn bạn có thể giải thích vị trí của bạn cho người đánh giá của bạn với các ví dụ về mã.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.