понедельник, 17 февраля 2014 г.

Convenient exceptions

C++11 introduced new language features that enable implementation of chained exceptions in general. In C++98 it was possible to implement exception chaining but only for your own exception hierarchy - for example, POCO exception class - and this solution was incomplete and cumbersome.
One of the main requirements for jet::exception is exception chaining. My full list of "ideal" exception features:
1. Exception chaining;
2. Stream-like interface for error message construction;
3. Ability to create hierarchy of exceptions;
4. Exception should hold information about location in source code where it was raised;
5. Convenient way to throw exception;
6. (Optionally) Stacktrace attached to exception;

I implemented exception chaining using std::current_exception. Implementation itself is not difficult but chaining changes the way how I use exceptions. Previously I used the code similar to the following when I wanted to implement exception translation:

try
{
    //...
}
catch(const std::exception& ex)
{
    throw my_error(std::string("Couldn't do stuff. Reason: ") + ex.what());
}

This "surrogate chaining" is not necessary anymore. But after I fixed all such places I found my test cases are broken because I expected "flatten" exception message. With this change I had to change my test cases. For better handling exception cases I introduced "exception validator" which checks one or more exceptions in the chain.
Consider the following code:

TEST(config_source, error_instance_shortcut_config_source)
{
    EXPECT_CONFIG_ERROR(
        config_source::from_string{"<config><app.. attr='value'/></config>"}.name("s1.xml").create(),
        equal
            ("Couldn't parse config 's1.xml'")
            ("Invalid '..' in element 'app..' in config source 's1.xml'. Expected format 'app_name..instance_name'"));
    EXPECT_CONFIG_ERROR(
        config_source::from_string{"<config><..i1 attr='value'/></config>"}.name("s1.xml").create(),
        start_with
            ("Couldn't parse")
            ("Invalid '..' in element '..i1'"));
}

Here you can see two forms of error validators - "equal" and "start_with".
The former requires that exception has specified number of messages and all messages are equal to expected messages. "Equal" is useful when exact error message match is required.
"Start_with" validator is used when user wants to avoid unnecessary typing. This check is successful if number of expected messages is less or equal to actual error messages and each expected message is started from string passed to validator.
I was also considering "regex" validator but then gave up this idea as overly complex.

Комментариев нет:

Отправить комментарий