การออกแบบคลาสยกเว้น


9

ฉันกำลังเขียนโค้ดไลบรารีขนาดเล็กและฉันมีปัญหากับการออกแบบการจัดการข้อยกเว้น ฉันต้องบอกว่าฉันยังคงสับสนกับคุณสมบัติของภาษา C ++ และพยายามอ่านให้มากที่สุดเท่าที่จะเป็นไปได้เพื่อทำความเข้าใจกับสิ่งที่ฉันต้องทำเพื่อทำงานกับคลาสยกเว้นอย่างเหมาะสม

ฉันตัดสินใจใช้system_errorวิธีการที่ได้แรงบันดาลใจจากการใช้งาน STL ของfuture_errorชั้นเรียน

ฉันมีการแจงนับที่มีรหัสข้อผิดพลาด:

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

และคลาสข้อยกเว้นเดียว (สำรองตามerror_categoryประเภทของโครงสร้างและทุกอย่างอื่นที่จำเป็นโดยsystem_errorรุ่น):

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

ฉันมีสถานการณ์จำนวนน้อยที่ฉันโยนข้อยกเว้นที่แสดงโดยการระบุรหัสข้อผิดพลาด

ด้านบนไม่ได้นั่งกับผู้ตรวจสอบคนหนึ่งของฉัน เขาเห็นว่าฉันควรสร้างลำดับชั้นของคลาสยกเว้นที่มีคลาสพื้นฐานที่ได้มาstd::runtime_errorเนื่องจากมีรหัสข้อผิดพลาดฝังอยู่ในเงื่อนไขผสมสิ่งต่าง ๆ - ข้อยกเว้นและรหัสข้อผิดพลาด - และมันน่าเบื่อกว่าที่จะจัดการกับประเด็น ของการจัดการ; ลำดับชั้นข้อยกเว้นจะอนุญาตให้ปรับแต่งข้อความแสดงข้อผิดพลาดได้ง่าย

หนึ่งในข้อโต้แย้งของฉันคือฉันต้องการทำให้มันง่ายห้องสมุดของฉันไม่จำเป็นต้องโยนข้อยกเว้นหลายประเภทและการปรับแต่งนั้นก็ง่ายในกรณีนี้เนื่องจากมีการจัดการโดยอัตโนมัติ - error_codeมีการerror_categoryเชื่อมโยงกับที่แปล รหัสไปยังข้อความผิดพลาดที่เหมาะสม

ฉันต้องบอกว่าฉันไม่ได้ปกป้องตัวเลือกของฉันได้ดีนักยืนยันถึงข้อเท็จจริงที่ว่าฉันยังคงมีความเข้าใจผิดบางประการเกี่ยวกับข้อยกเว้น C ++

ฉันต้องการทราบว่าการออกแบบของฉันเหมาะสมหรือไม่ อะไรคือข้อดีของวิธีอื่นที่ฉันเลือกเพราะฉันต้องยอมรับว่าฉันไม่เห็นด้วย? ฉันจะปรับปรุงอะไรได้บ้าง


2
ฉันมักจะเห็นด้วยในหลักการกับผู้ตรวจสอบของคุณ (การผสมรหัสข้อผิดพลาดและข้อยกเว้นนั้นไม่มีประโยชน์จริง ๆ ) แต่ถ้าคุณไม่มีห้องสมุดขนาดใหญ่ที่มีลำดับชั้นมากก็ไม่เป็นประโยชน์เช่นกัน ข้อยกเว้นพื้นฐานที่มีสตริงข้อความมีข้อยกเว้นแยกต่างหากถ้าตัวดักจับของข้อยกเว้นนั้นอาจใช้ข้อยกเว้นที่ไม่ซ้ำกันเพื่อแก้ไขปัญหา
Martin York

คำตอบ:


9

ฉันคิดว่าเพื่อนร่วมงานของคุณถูกต้อง: คุณออกแบบกรณียกเว้นของคุณโดยพิจารณาจากความง่ายในการนำไปใช้ภายในลำดับชั้นไม่ใช่ตามความต้องการการจัดการข้อยกเว้นของรหัสลูกค้า

ด้วยประเภทข้อยกเว้นหนึ่งประเภทและการแจงนับสำหรับเงื่อนไขข้อผิดพลาด (โซลูชันของคุณ) หากรหัสลูกค้าจำเป็นต้องจัดการกับกรณีข้อผิดพลาดเดียว (ตัวอย่างเช่นmy_errc::error_x) พวกเขาจะต้องเขียนโค้ดดังนี้:

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

ด้วยข้อยกเว้นหลายประเภท (มีฐานร่วมกันสำหรับลำดับชั้นทั้งหมด) คุณสามารถเขียน:

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

ที่คลาสยกเว้นมีลักษณะเช่นนี้:

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

เมื่อเขียนห้องสมุดควรมุ่งเน้นไปที่ความสะดวกในการใช้งานไม่จำเป็นต้องง่ายในการปรับใช้ภายใน

คุณควรประนีประนอมความง่ายในการใช้งาน (ลักษณะของรหัสลูกค้า) เมื่อความพยายามในการทำมันถูกต้องในห้องสมุดเป็นสิ่งต้องห้าม


0

ฉันเห็นด้วยกับรีวิวของคุณและกับ @utnapistim คุณสามารถใช้system_errorวิธีการเมื่อคุณใช้สิ่งต่าง ๆ ข้ามแพลตฟอร์มเมื่อข้อผิดพลาดบางอย่างต้องการการจัดการพิเศษ แต่ถึงแม้ในกรณีนี้มันไม่ได้เป็นทางออกที่ดี แต่มีวิธีแก้ปัญหาที่น้อยกว่า

อีกหนึ่งสิ่ง. เมื่อสร้างลำดับชั้นข้อยกเว้นอย่าทำให้ลึกมาก สร้างคลาสยกเว้นเฉพาะที่สามารถประมวลผลโดยไคลเอนต์ ในกรณีส่วนใหญ่ผมใช้เพียงและstd::runtime_error std::logic_errorฉันโยนstd::runtime_errorเมื่อเกิดข้อผิดพลาดและฉันไม่สามารถทำอะไรได้เลย (อุปกรณ์นำผู้ใช้ออกจากคอมพิวเตอร์เขาลืมแอปพลิเคชันนั้นยังทำงานอยู่) และstd::logic_errorเมื่อโปรแกรมตรรกะทำงานผิดพลาด (ผู้ใช้พยายามลบบันทึกจากฐานข้อมูลที่ไม่มีอยู่ สามารถตรวจสอบได้เพื่อให้เขาได้รับข้อผิดพลาดเชิงตรรกะ)

และในฐานะนักพัฒนาห้องสมุดให้คำนึงถึงความต้องการของผู้ใช้ของคุณ ลองใช้ด้วยตัวคุณเองแล้วคิดดูว่ามันจะสะดวกสบายสำหรับคุณหรือไม่ กว่าที่คุณจะสามารถอธิบายตำแหน่งของคุณให้กับผู้ตรวจสอบของคุณด้วยตัวอย่างของรหัส

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.