Tuesday, February 10, 2015

C++ Status: Little error handling idea [UI apps mainly]


Overview

After working some time in UI applications I always end with the same issues of how to handle the errors efficiently and clean. I decide to implement this little wrapper and use it as the base for the different modules of the projects. This is mainly for the ones who don't use exceptions (because they don't like it or because they cannot).

The wrapper "Status"

The main motivation to create this wrapper was the idea to have the information of the error (human readable) with the error itself and be able to propagate it efficiently over the functions (like exceptions do). It is like an intermediate solution between "old style error code checking" and exceptions.
To solve this problem I used and saw two main ways:
1) Use a error code (enum or whatever) and then map it to a predefined string list that shows the "information" to the user about what happened.
2) The modules (classes) had a extra method where returns the "detailed" information about the last error (getLastStringError() or similar).

In some cases (1) could not be the best option because the error is not detailed enough since is a "common" list where the same information is used in different scenarios.
The thing with (2) is that adding a additional method for each class and have a buffer where we save the last error info is not clean nor nice (at least for me :) ).

So, to solve this two things and do it as efficient as possible, since we don't want to loose performance, never, never loose performance, I decided to implement this wrapper. The main idea is that the Status wrapper will have the same size as an integer (like an error code) but will contain more information.
To be able to save the "detailed error description" we will not put the string in the wrapper itself but an index to a table of shared strings, where we will put the real description.
Here is the data we save in the class:


class Status
{
    //     ...


    // this is all the information we will hold to maintain the same efficiency
    // than normal integers.
    // the scheme as next
    // bits: 0..3   (4 bits)    => Severity (none, critical, normal, low)
    // bits: 4..15  (12 bits)   => StatusCode (error code)
    // bits: 16..31 (16 bits)   => Internal index referencing table

    struct Data {
        // define the invalid index
        static const unsigned short INV_INDEX = (1 << 15);

        unsigned int code       : 12;
        unsigned int severity   : 4;
        unsigned int index      : 16;

        Data(unsigned int c, unsigned int sev, unsigned short i) :
            code(c), severity(sev), index(i)
        {}
    } data;
};    

As we can see we are only storing 32 bits in the class (like an integer in some platforms). This way when we return the status we are just returning an 32 bit structure and will be fast to copy it, also, whenever we go up with the message (until the UI code to show it to the user), we never duplicate or copy the string itself, and here is where we earn some performance in comparison with other methods (copying the string up and up).
The other thing I decided to add is the severity of the error, this is useful in some cases when we just need to know if we can continue with code execution or return immediately, this way we can maintain a much more simpler and clean code!.

The Status can be constructed in different ways providing 3 main manners of using it: like booleans, like old error codes style, or error code with description.

Lets see the next example:



Status
coreMethod1(int arg1, int arg2)
{
    // check arg validity
    if (arg1 < 0) {
        // invalid arg
        return Status(StatusCode::STC_INVALID_ARGS,
                      StatusSeverity::STS_NORMAL,
                      "The arg1 is less than 0 so we cannot proceed!");
    }

    // everything is fine, just return no error (automatically constructed
    // from bool).
    return true;
}

// other methods...

Status
middleLayerMethod(void)
{
    // ...

    // suppose that we don't want to continue if coreMethod1 return a critical method
    Status st = coreMethod1(v1, v2);
    ST_CHECK_CRITICAL(st);

    // but know suppose we have multiple methods to init that are not
    // so important if they fails, but still we want to report to the user
    // the information:
    st += coreMethod2();
    st += coreMethod3();
    st += coreMethod4();

    return st;
}

void
uiLogicLevel(void)
{
    Status st = middleLayerMethod();
    if (!st) {
        // handle status / show description to the user if it fails
    }

}

Here we can see two of the benefits of this wrapper:

1) If the Status is moved from different functions (returned, copied, whatever), the copy of it is trivial, "no performance hit at all" (almost, but for sure less than a string being copied up).

2) We can accumulate and return the "information" avoiding ugly branches in certain cases (if the error is not critical of course). This second option could (should) be configured depending the needs of the project, this is implemented in the operator+= method



///
/// \brief operator += will be used to mix different status, we will
///        concatenate the strings and also maintain the highest priority
///        and the error. In case that the error is different, we will
///        simply set it to internal error (this is maybe not what you 
///        need/ want).
/// \param other    The other status we want to mix with
/// \return the mixed new status (this)
///
inline Status&
operator+=(const Status& other);




Initialization

To be able to use it you must first initialize the StatusHandler, that is the class who contains all the shared strings for the Status instances.




// init the handler
static StatusHandler statusHandler;
// set the global handler to be used by all the Status instances
Status::s_shandler = &statusHandler;

Then you can start using it normally. Note that this is not prepared for multithreading since we are sharing the strings table.


Project Source

This little project and wrapper is located in github so if you want to take a look.
Hope it helps someone!

No comments:

Post a Comment