Monday, December 16, 2013

Odds and ends: error reporting

Welcome back.

In this post, some old content from appendix A of the book, Odds and ends. Yes, this is what's affectionately known as a filler post. It's technical, rather than finance-related; but it's about a part of coding that everybody bumps into eventually, and I hope that you will find it interesting.

Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the widgets for that are in the sidebar, at the top right of the page. Also, make sure to check my Training page.

Error reporting

There are a great many places in the library where some condition must be checked. Rather than doing it as
    if (i >= v.size())
        throw Error("index out of range");
we wanted to express the intent more clearly, i.e., with a syntax like
    require(i < v.size(), "index out of range");
where on the one hand, we write the condition to be satisfied and not its opposite; and on the other hand, terms such as require, ensure, or assert—which have a somewhat canonical meaning in programming—would tell whether we're checking a precondition, a postcondition, or a programmer error.

We provided the desired syntax with macros. "Get behind thee", I hear you say. True, macros have a bad name, and in fact they caused us a problem or two, as we'll see below. But in this case, functions had a big disadvantage: they evaluate all their arguments. Many times, we want to create a moderately complex error message, such as:
    require(i < v.size(),
            "index " + to_string(i) + " out of range");
If require were a function, the message would be built whether or not the condition is satisfied, causing a performance hit that would not be acceptable. With a macro, the above is textually replaced by something like:
    if (!(i < v.size()))
        throw Error("index " + to_string(i) + " out of range");
which builds the message only if the condition is violated.

Listing A.3 shows the current version of one of the macros, namely, QL_REQUIRE; the other macros are defined in a similar way.

Listing A.3: Definition of the QL_REQUIRE macro.
    #define QL_REQUIRE(condition,message) \
    if (!(condition)) { \
        std::ostringstream _ql_msg_stream; \
        _ql_msg_stream << message; \
        throw QuantLib::Error(__FILE__,__LINE__, \
                              BOOST_CURRENT_FUNCTION,
                              _ql_msg_stream.str()); \
    } else

Its definition has a few more bells and whistles that might be expected. Firstly, we use an ostringstream to build the message string. This allows one to use a syntax like
    QL_REQUIRE(i < v.size(),
              "index " << i << " out of range");
to build the message (you can see how that works by replacing the pieces in the macro body). Secondly, the Error instance is passed the name of the current function as well as the line and file where the error is thrown. Depending on a compilation flag, this information can be included in the error message to help developers; the default behavior is to not include it, since it's of little utility for users. Lastly, you might be wondering why we added an else at the end of the macro. That is due to a common macro pitfall, namely, its lack of a lexical scope. The else is needed by code such as
    if (someCondition())
        QL_REQUIRE(i < v.size(), "index out of bounds");
    else
        doSomethingElse();
Without the else in the macro, the above would not work as expected. Instead, the else in the code would pair with the if in the macro and the code would translate into
    if (someCondition()) {
        if (!(i < v.size()))
            throw Error("index out of bounds");
        else
            doSomethingElse();
    }
which has a different behavior.

As a final note, I have to describe a disadvantage of these macros. As they are now, they throw exceptions that can only return their contained message; no inspector is defined for any other relevant data. For instance, although an out-of-bounds message might include the passed index, no other method in the exception returns the index as an integer. Therefore, the information can be displayed to the user but would be unavailable to recovery code in catch clauses—unless one parses the message, that is; but that is hardly worth the effort. There's no planned solution at this time, so drop us a line if you have one.

Share this post: